1# ===----------------------------------------------------------------------===## 2# 3# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4# See https://llvm.org/LICENSE.txt for license information. 5# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6# 7# ===----------------------------------------------------------------------===## 8 9# Test that all named declarations with external linkage match the 10# exported declarations in their associated module partition. 11# Then it tests the sum of the exported declarations in the module 12# partitions matches the export of the std module. 13 14# Note the test of the std module requires all partitions to be tested 15# first. Since lit tests have no dependencies, this means the test needs 16# to be one monolitic test. Since the test doesn't take very long it's 17# not a huge issue. 18 19# RUN: %{python} %s %{libcxx}/utils 20 21import sys 22 23sys.path.append(sys.argv[1]) 24from libcxx.header_information import module_headers 25from libcxx.header_information import header_restrictions 26 27BLOCKLIT = ( 28 "" # block Lit from interpreting a RUN/XFAIL/etc inside the generation script 29) 30 31# Ignore several declarations found in the includes. 32# 33# Part of these items are bugs other are not yet implemented features. 34SkipDeclarations = dict() 35 36# See comment in the header. 37SkipDeclarations["cuchar"] = ["std::mbstate_t", "std::size_t"] 38 39# Not in the synopsis. 40SkipDeclarations["cwchar"] = ["std::FILE"] 41 42# The operators are added for private types like __iom_t10. 43SkipDeclarations["iomanip"] = ["std::operator<<", "std::operator>>"] 44 45SkipDeclarations["iosfwd"] = ["std::ios_base", "std::vector"] 46 47# This header also provides declarations in the namespace that might be 48# an error. 49SkipDeclarations["filesystem"] = [ 50 "std::filesystem::operator==", 51 "std::filesystem::operator!=", 52] 53 54# This is a specialization for a private type 55SkipDeclarations["iterator"] = ["std::pointer_traits"] 56 57# TODO MODULES 58# This definition is declared in string and defined in istream 59# This declaration should be part of string 60SkipDeclarations["istream"] = ["std::getline"] 61 62# P1614 (at many places) and LWG3519 too. 63SkipDeclarations["random"] = [ 64 "std::operator!=", 65 # LWG3519 makes these hidden friends. 66 # Note the older versions had the requirement of these operations but not in 67 # the synopsis. 68 "std::operator<<", 69 "std::operator>>", 70 "std::operator==", 71] 72 73# Declared in the forward header since std::string uses std::allocator 74SkipDeclarations["string"] = ["std::allocator"] 75# TODO MODULES remove zombie names 76# https://libcxx.llvm.org/Status/Cxx20.html#note-p0619 77SkipDeclarations["memory"] = [ 78 "std::return_temporary_buffer", 79 "std::get_temporary_buffer", 80] 81 82# TODO MODULES this should be part of ios instead 83SkipDeclarations["streambuf"] = ["std::basic_ios"] 84 85# include/__type_traits/is_swappable.h 86SkipDeclarations["type_traits"] = [ 87 "std::swap", 88 # TODO MODULES gotten through __functional/unwrap_ref.h 89 "std::reference_wrapper", 90] 91 92# Add declarations in headers. 93# 94# Some headers have their defines in a different header, which may have 95# additional declarations. 96ExtraDeclarations = dict() 97# This declaration is in the ostream header. 98ExtraDeclarations["system_error"] = ["std::operator<<"] 99 100# Adds an extra header file to scan 101# 102# 103ExtraHeader = dict() 104# locale has a file and not a subdirectory 105ExtraHeader["locale"] = "v1/__locale$" 106ExtraHeader["thread"] = "v1/__threading_support$" 107ExtraHeader["ranges"] = "v1/__fwd/subrange.h$" 108 109# The extra header is needed since two headers are required to provide the 110# same definition. 111ExtraHeader["functional"] = "v1/__compare/compare_three_way.h$" 112 113# newline needs to be escaped for the module partition output. 114nl = '\\\\n' 115 116# Create empty file with all parts. 117print( 118 f"""\ 119//--- module_std.sh.cpp 120// UNSUPPORTED{BLOCKLIT}: c++03, c++11, c++14, c++17 121// UNSUPPORTED{BLOCKLIT}: libcpp-has-no-std-modules 122// UNSUPPORTED{BLOCKLIT}: clang-modules-build 123 124// REQUIRES{BLOCKLIT}: has-clang-tidy 125 126// The GCC compiler flags are not always compatible with clang-tidy. 127// UNSUPPORTED{BLOCKLIT}: gcc 128 129// RUN{BLOCKLIT}: echo -n > %t.all_partitions 130""" 131) 132 133# Validate all module parts. 134for header in module_headers: 135 # Some headers cannot be included when a libc++ feature is disabled. 136 # In that case include the header conditionally. The header __config 137 # ensures the libc++ feature macros are available. 138 if header in header_restrictions: 139 include = ( 140 f"#include <__config>{nl}" 141 + f"#if {header_restrictions[header]}{nl}" 142 + f"# include <{header}>{nl}" 143 + f"#endif{nl}" 144 ) 145 elif header == "chrono": 146 # When localization is disabled the header string is not included. 147 # When string is included chrono's operator""s is a named declaration 148 # using std::chrono_literals::operator""s; 149 # else it is a named declaration 150 # using std::operator""s; 151 # TODO MODULES investigate why 152 include = f"#include <string>{nl}#include <chrono>{nl}" 153 else: 154 include = f"#include <{header}>{nl}" 155 156 # Generate a module partition for the header module includes. This 157 # makes it possible to verify that all headers export all their 158 # named declarations. 159 print( 160 f"// RUN{BLOCKLIT}: echo -e \"" 161 f"module;{nl}" 162 f"{include}" 163 f"{nl}" 164 f"// Use __libcpp_module_<HEADER> to ensure that modules {nl}" 165 f"// are not named as keywords or reserved names.{nl}" 166 f"export module std:__libcpp_module_{header};{nl}" 167 f'#include \\"%{{module}}/std/{header}.inc\\"{nl}' 168 f"\" > %t.{header}.cppm") 169 170 # Dump the information as found in the module's cppm file. 171 print( 172 f"// RUN{BLOCKLIT}: %{{clang-tidy}} %t.{header}.cppm " 173 " --checks='-*,libcpp-header-exportable-declarations' " 174 " -config='{CheckOptions: [ " 175 " {" 176 " key: libcpp-header-exportable-declarations.Filename, " 177 f" value: {header}.inc" 178 " }, {" 179 " key: libcpp-header-exportable-declarations.FileType, " 180 " value: ModulePartition" 181 " }, " 182 " ]}' " 183 " --load=%{test-tools}/clang_tidy_checks/libcxx-tidy.plugin " 184 " -- %{flags} %{compile_flags} " 185 f"| sort > %t.{header}.module" 186 ) 187 print(f"// RUN{BLOCKLIT}: cat %t.{header}.module >> %t.all_partitions") 188 189 # Dump the information as found in the module by using the header file(s). 190 skip_declarations = " ".join(SkipDeclarations.get(header, [])) 191 if skip_declarations: 192 skip_declarations = ( 193 "{" 194 " key: libcpp-header-exportable-declarations.SkipDeclarations, " 195 f' value: "{skip_declarations}" ' 196 "}, " 197 ) 198 199 extra_declarations = " ".join(ExtraDeclarations.get(header, [])) 200 if extra_declarations: 201 extra_declarations = ( 202 " {" 203 " key: libcpp-header-exportable-declarations.ExtraDeclarations, " 204 f' value: "{extra_declarations}" ' 205 "}, " 206 ) 207 208 extra_header = ExtraHeader.get(header, "") 209 if extra_header: 210 extra_header = ( 211 "{" 212 " key: libcpp-header-exportable-declarations.ExtraHeader, " 213 f' value: "{extra_header}" ' 214 "}, " 215 ) 216 217 # Clang-tidy needs a file input 218 print(f'// RUN{BLOCKLIT}: echo -e "' f"{include}" f'" > %t.{header}.cpp') 219 print( 220 f"// RUN{BLOCKLIT}: %{{clang-tidy}} %t.{header}.cpp " 221 " --checks='-*,libcpp-header-exportable-declarations' " 222 " -config='{CheckOptions: [ " 223 f" {{key: libcpp-header-exportable-declarations.Filename, value: {header}}}, " 224 " {key: libcpp-header-exportable-declarations.FileType, value: Header}, " 225 f" {skip_declarations} {extra_declarations} {extra_header}, " 226 " ]}' " 227 " --load=%{test-tools}/clang_tidy_checks/libcxx-tidy.plugin " 228 " -- %{flags} %{compile_flags} " 229 f" | sort > %t.{header}.include" 230 ) 231 232 # Compare the cppm and header file(s) return the same results. 233 print(f"// RUN{BLOCKLIT}: diff -u %t.{header}.module %t.{header}.include") 234 235 236# Merge the data of the parts 237print(f"// RUN{BLOCKLIT}: sort -u -o %t.all_partitions %t.all_partitions") 238 239# Dump the information as found in std.cppm. 240print( 241 f"// RUN{BLOCKLIT}: %{{clang-tidy}} %{{module}}/std.cppm " 242 " --checks='-*,libcpp-header-exportable-declarations' " 243 " -config='{CheckOptions: [ " 244 " {key: libcpp-header-exportable-declarations.Header, value: std.cppm}, " 245 " {key: libcpp-header-exportable-declarations.FileType, value: Module}, " 246 " ]}' " 247 f" --load=%{{test-tools}}/clang_tidy_checks/libcxx-tidy.plugin " 248 " -- %{flags} %{compile_flags} " 249 " | sort > %t.module" 250) 251 252 253# Compare the sum of the parts with the main module. 254print(f"// RUN{BLOCKLIT}: diff -u %t.all_partitions %t.module") 255 256# Basic smoke test. Import a module and try to compile when using all 257# exported names. This validates the clang-tidy script does not accidentally 258# add named declarations to the list that are not available. 259print(f"// RUN{BLOCKLIT}: echo 'import std;' > %t.compile.pass.cpp") 260print(f"// RUN{BLOCKLIT}: cat %t.all_partitions >> %t.compile.pass.cpp") 261print(f"// RUN{BLOCKLIT}: %{{cxx}} %{{flags}} %{{compile_flags}} -fsyntax-only %t.compile.pass.cpp") 262