diff --git a/CMakeLists.txt b/CMakeLists.txt index 25ae28707..469c161c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.1...3.14) ## PROJECT ## name and version ## -project(nlohmann_json VERSION 3.11.3 LANGUAGES CXX) +project(nlohmann_json VERSION 3.11.3 LANGUAGES C CXX) ## ## MAIN_PROJECT CHECK @@ -48,6 +48,7 @@ option(JSON_LegacyDiscardedValueComparison "Enable legacy discarded value compar option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT}) option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON) option(JSON_SystemInclude "Include as system headers (skip for clang-tidy)." OFF) +option(JSON_ClangTidyPlugin "Build clang-tidy plug-in." OFF) if (JSON_CI) include(ci) @@ -158,6 +159,12 @@ CONFIGURE_FILE( @ONLY ) +if (JSON_ClangTidyPlugin) + find_package(LLVM REQUIRED CONFIG) + find_package(Clang REQUIRED CONFIG) + add_subdirectory(clang_tidy_plugin) +endif() + ## ## TESTS ## create and configure the unit test target diff --git a/clang_tidy_plugin/CMakeLists.txt b/clang_tidy_plugin/CMakeLists.txt new file mode 100644 index 000000000..9ef6e4086 --- /dev/null +++ b/clang_tidy_plugin/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(NlohmannJsonClangTidyPlugin MODULE "") +target_compile_options(NlohmannJsonClangTidyPlugin PRIVATE -fno-rtti) +target_include_directories(NlohmannJsonClangTidyPlugin + PRIVATE + ${CLANG_INCLUDE_DIRS} + ${LLVM_INCLUDE_DIRS} +) +target_sources(NlohmannJsonClangTidyPlugin PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/Module.cpp + ${CMAKE_CURRENT_LIST_DIR}/ModernizeNlohmannJsonExplicitConversionsCheck.cpp +) diff --git a/clang_tidy_plugin/ModernizeNlohmannJsonExplicitConversionsCheck.cpp b/clang_tidy_plugin/ModernizeNlohmannJsonExplicitConversionsCheck.cpp new file mode 100644 index 000000000..220963bea --- /dev/null +++ b/clang_tidy_plugin/ModernizeNlohmannJsonExplicitConversionsCheck.cpp @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2025 Mike Crowe +// SPDX-License-Identifier: MIT + +#include "ModernizeNlohmannJsonExplicitConversionsCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { + +void NlohmannJsonExplicitConversionsCheck::registerMatchers( + MatchFinder *Finder) { + auto Matcher = + cxxMemberCallExpr( + on(expr().bind("arg")), + callee(cxxConversionDecl(ofClass(hasName("nlohmann::basic_json"))) + .bind("conversionDecl"))) + .bind("conversionCall"); + Finder->addMatcher(Matcher, this); +} + +void NlohmannJsonExplicitConversionsCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Decl = + Result.Nodes.getNodeAs("conversionDecl"); + const auto *Call = + Result.Nodes.getNodeAs("conversionCall"); + const auto *Arg = Result.Nodes.getNodeAs("arg"); + + const QualType DestinationType = Decl->getConversionType(); + std::string DestinationTypeStr = + DestinationType.getAsString(Result.Context->getPrintingPolicy()); + if (DestinationTypeStr == "std::basic_string") + DestinationTypeStr = "std::string"; + + const SourceRange ExprRange = Call->getSourceRange(); + if (!ExprRange.isValid()) + return; + + bool Deref = false; + if (const auto *Op = llvm::dyn_cast(Arg); + Op && Op->getOpcode() == UO_Deref) + Deref = true; + else if (const auto *Op = llvm::dyn_cast(Arg); + Op && Op->getOperator() == OO_Star) + Deref = true; + + llvm::StringRef SourceText = clang::Lexer::getSourceText( + clang::CharSourceRange::getTokenRange(ExprRange), *Result.SourceManager, + Result.Context->getLangOpts()); + + if (Deref) + SourceText.consume_front("*"); + + const std::string ReplacementText = + (llvm::Twine(SourceText) + (Deref ? "->" : ".") + "get<" + + DestinationTypeStr + ">()") + .str(); + diag(Call->getExprLoc(), + "implicit nlohmann::json conversion to %0 should be explicit") + << DestinationTypeStr + << FixItHint::CreateReplacement(CharSourceRange::getTokenRange(ExprRange), + ReplacementText); +} + +} // namespace clang::tidy::modernize diff --git a/clang_tidy_plugin/ModernizeNlohmannJsonExplicitConversionsCheck.h b/clang_tidy_plugin/ModernizeNlohmannJsonExplicitConversionsCheck.h new file mode 100644 index 000000000..11eb482e5 --- /dev/null +++ b/clang_tidy_plugin/ModernizeNlohmannJsonExplicitConversionsCheck.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2025 Mike Crowe +// SPDX-License-Identifier: MIT + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_NLOHMANNJSONEXPLICITCONVERSIONSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_NLOHMANNJSONEXPLICITCONVERSIONSCHECK_H + +#include "clang-tidy/ClangTidyCheck.h" + +namespace clang::tidy::modernize { + +/// Convert implicit conversions via operator ValueType() from nlohmann::json to +/// explicit calls to the get<> method because the next major version of the +/// library will remove support for implicit conversions. +/// +/// For the user-facing documentation see: +/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/nlohmann-json-explicit-conversions.html +class NlohmannJsonExplicitConversionsCheck : public ClangTidyCheck { +public: + NlohmannJsonExplicitConversionsCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } +}; + +} // namespace clang::tidy::modernize + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_NLOHMANNJSONEXPLICITCONVERSIONSCHECK_H diff --git a/clang_tidy_plugin/Module.cpp b/clang_tidy_plugin/Module.cpp new file mode 100644 index 000000000..1ec0d852a --- /dev/null +++ b/clang_tidy_plugin/Module.cpp @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2025 Mike Crowe +// SPDX-License-Identifier: MIT + +#include "clang-tidy/ClangTidyModule.h" +#include "clang-tidy/ClangTidyModuleRegistry.h" +#include "ModernizeNlohmannJsonExplicitConversionsCheck.h" + +namespace { + + using namespace clang::tidy; + +class NlohmannJsonChecksModule : public ClangTidyModule +{ +public: + void addCheckFactories(ClangTidyCheckFactories& CheckFactories) override + { + CheckFactories.registerCheck( + "modernize-nlohmann-json-explicit-conversions"); + } +}; + +} // namespace + +namespace clang::tidy { + +// Register the module using this statically initialized variable. + static ClangTidyModuleRegistry::Add<::NlohmannJsonChecksModule> nlohmannJsonChecksInit( + "nlohmann-json-checks-module", + "Adds 'modernize-nlohmann-json-explicit-conversions' check."); + +} // namespace clang::tidy diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a153a6924..47ee19086 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -168,6 +168,10 @@ json_test_add_test_for(src/unit-comparison.cpp add_subdirectory(abi) +if (JSON_ClangTidyPlugin) + add_subdirectory(clang_tidy_plugin) +endif() + ############################################################################# # Test the generated build configs ############################################################################# diff --git a/tests/clang_tidy_plugin/CMakeLists.txt b/tests/clang_tidy_plugin/CMakeLists.txt new file mode 100644 index 000000000..330153a2c --- /dev/null +++ b/tests/clang_tidy_plugin/CMakeLists.txt @@ -0,0 +1,10 @@ +include(CTest) + +if(NOT LLVM_TOOLS_BINARY_DIR) + message(FATAL_ERROR "Could not determine LLVM binary directory") +endif() + +add_test(NAME modernize-nlohmann-json-explicit-conversions-check COMMAND ${CMAKE_COMMAND} -E env PATH=${LLVM_TOOLS_BINARY_DIR}:$ENV{PATH} + ${CMAKE_CURRENT_LIST_DIR}/check_clang_tidy.py -std=c++17 + -plugin=$ + ${CMAKE_CURRENT_LIST_DIR}/modernize-nlohmann-json-explicit-conversions.cpp modernize-nlohmann-json-explicit-conversions temp) diff --git a/tests/clang_tidy_plugin/check_clang_tidy.py b/tests/clang_tidy_plugin/check_clang_tidy.py new file mode 100755 index 000000000..f5f6f17af --- /dev/null +++ b/tests/clang_tidy_plugin/check_clang_tidy.py @@ -0,0 +1,397 @@ +#!/usr/bin/env python3 +# +# ===- check_clang_tidy.py - ClangTidy Test Helper ------------*- python -*--===# +# +# 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 +# +# ===------------------------------------------------------------------------===# +# +# This file was taken from llvm-project and tweaked very slightly to +# add the ability to load a plugin so that it can be used for testing +# clang-tidy plugins. + +""" +ClangTidy Test Helper +===================== + +This script is used to simplify writing, running, and debugging tests compatible +with llvm-lit. By default it runs clang-tidy in fix mode and uses FileCheck to +verify messages and/or fixes. + +For debugging, with --export-fixes, the tool simply exports fixes to a provided +file and does not run FileCheck. + +Extra arguments, those after the first -- if any, are passed to either +clang-tidy or clang: +* Arguments between the first -- and second -- are clang-tidy arguments. + * May be only whitespace if there are no clang-tidy arguments. + * clang-tidy's --config would go here. +* Arguments after the second -- are clang arguments + +Examples +-------- + + // RUN: %check_clang_tidy %s llvm-include-order %t -- -- -isystem %S/Inputs + +or + + // RUN: %check_clang_tidy %s llvm-include-order --export-fixes=fixes.yaml %t -std=c++20 + +Notes +----- + -std=c++(98|11|14|17|20)-or-later: + This flag will cause multiple runs within the same check_clang_tidy + execution. Make sure you don't have shared state across these runs. +""" + +import argparse +import os +import pathlib +import re +import subprocess +import sys + + +def write_file(file_name, text): + with open(file_name, "w", encoding="utf-8") as f: + f.write(text) + f.truncate() + + +def try_run(args, raise_error=True): + try: + process_output = subprocess.check_output(args, stderr=subprocess.STDOUT).decode( + errors="ignore" + ) + except subprocess.CalledProcessError as e: + process_output = e.output.decode(errors="ignore") + print("%s failed:\n%s" % (" ".join(args), process_output)) + if raise_error: + raise + return process_output + + +# This class represents the appearance of a message prefix in a file. +class MessagePrefix: + def __init__(self, label): + self.has_message = False + self.prefixes = [] + self.label = label + + def check(self, file_check_suffix, input_text): + self.prefix = self.label + file_check_suffix + self.has_message = self.prefix in input_text + if self.has_message: + self.prefixes.append(self.prefix) + return self.has_message + + +class CheckRunner: + def __init__(self, args, extra_args): + self.resource_dir = args.resource_dir + self.assume_file_name = args.assume_filename + self.input_file_name = args.input_file_name + self.check_name = args.check_name + self.temp_file_name = args.temp_file_name + self.original_file_name = self.temp_file_name + ".orig" + self.expect_clang_tidy_error = args.expect_clang_tidy_error + self.std = args.std + self.plugin = args.plugin + self.check_suffix = args.check_suffix + self.input_text = "" + self.has_check_fixes = False + self.has_check_messages = False + self.has_check_notes = False + self.expect_no_diagnosis = False + self.export_fixes = args.export_fixes + self.fixes = MessagePrefix("CHECK-FIXES") + self.messages = MessagePrefix("CHECK-MESSAGES") + self.notes = MessagePrefix("CHECK-NOTES") + + file_name_with_extension = self.assume_file_name or self.input_file_name + _, extension = os.path.splitext(file_name_with_extension) + if extension not in [".c", ".hpp", ".m", ".mm"]: + extension = ".cpp" + self.temp_file_name = self.temp_file_name + extension + + self.clang_extra_args = [] + self.clang_tidy_extra_args = extra_args + if "--" in extra_args: + i = self.clang_tidy_extra_args.index("--") + self.clang_extra_args = self.clang_tidy_extra_args[i + 1 :] + self.clang_tidy_extra_args = self.clang_tidy_extra_args[:i] + + # If the test does not specify a config style, force an empty one; otherwise + # auto-detection logic can discover a ".clang-tidy" file that is not related to + # the test. + if not any( + [re.match("^-?-config(-file)?=", arg) for arg in self.clang_tidy_extra_args] + ): + self.clang_tidy_extra_args.append("--config={}") + + if extension in [".m", ".mm"]: + self.clang_extra_args = [ + "-fobjc-abi-version=2", + "-fobjc-arc", + "-fblocks", + ] + self.clang_extra_args + + if extension in [".cpp", ".hpp", ".mm"]: + self.clang_extra_args.append("-std=" + self.std) + + # Tests should not rely on STL being available, and instead provide mock + # implementations of relevant APIs. + self.clang_extra_args.append("-nostdinc++") + + if self.resource_dir is not None: + self.clang_extra_args.append("-resource-dir=%s" % self.resource_dir) + + def read_input(self): + with open(self.input_file_name, "r", encoding="utf-8") as input_file: + self.input_text = input_file.read() + + def get_prefixes(self): + for suffix in self.check_suffix: + if suffix and not re.match("^[A-Z0-9\\-]+$", suffix): + sys.exit( + 'Only A..Z, 0..9 and "-" are allowed in check suffixes list,' + + ' but "%s" was given' % suffix + ) + + file_check_suffix = ("-" + suffix) if suffix else "" + + has_check_fix = self.fixes.check(file_check_suffix, self.input_text) + self.has_check_fixes = self.has_check_fixes or has_check_fix + + has_check_message = self.messages.check(file_check_suffix, self.input_text) + self.has_check_messages = self.has_check_messages or has_check_message + + has_check_note = self.notes.check(file_check_suffix, self.input_text) + self.has_check_notes = self.has_check_notes or has_check_note + + if has_check_note and has_check_message: + sys.exit( + "Please use either %s or %s but not both" + % (self.notes.prefix, self.messages.prefix) + ) + + if not has_check_fix and not has_check_message and not has_check_note: + self.expect_no_diagnosis = True + + expect_diagnosis = ( + self.has_check_fixes or self.has_check_messages or self.has_check_notes + ) + if self.expect_no_diagnosis and expect_diagnosis: + sys.exit( + "%s, %s or %s not found in the input" + % ( + self.fixes.prefix, + self.messages.prefix, + self.notes.prefix, + ) + ) + assert expect_diagnosis or self.expect_no_diagnosis + + def prepare_test_inputs(self): + # Remove the contents of the CHECK lines to avoid CHECKs matching on + # themselves. We need to keep the comments to preserve line numbers while + # avoiding empty lines which could potentially trigger formatting-related + # checks. + cleaned_test = re.sub("// *CHECK-[A-Z0-9\\-]*:[^\r\n]*", "//", self.input_text) + write_file(self.temp_file_name, cleaned_test) + write_file(self.original_file_name, cleaned_test) + + def run_clang_tidy(self): + args = ( + [ + "clang-tidy", + self.temp_file_name, + ] + + [ + ( + "-fix" + if self.export_fixes is None + else "--export-fixes=" + self.export_fixes + ) + ] + + ([ "--load=%s" % self.plugin ] if self.plugin else []) + + [ + "--checks=-*," + self.check_name, + ] + + self.clang_tidy_extra_args + + ["--"] + + self.clang_extra_args + ) + if self.expect_clang_tidy_error: + args.insert(0, "not") + print("Running " + repr(args) + "...") + clang_tidy_output = try_run(args) + print("------------------------ clang-tidy output -----------------------") + print( + clang_tidy_output.encode(sys.stdout.encoding, errors="replace").decode( + sys.stdout.encoding + ) + ) + print("------------------------------------------------------------------") + + diff_output = try_run( + ["diff", "-u", self.original_file_name, self.temp_file_name], False + ) + print("------------------------------ Fixes -----------------------------") + print(diff_output) + print("------------------------------------------------------------------") + return clang_tidy_output + + def check_no_diagnosis(self, clang_tidy_output): + if clang_tidy_output != "": + sys.exit("No diagnostics were expected, but found the ones above") + + def check_fixes(self): + if self.has_check_fixes: + try_run( + [ + "FileCheck", + "-input-file=" + self.temp_file_name, + self.input_file_name, + "-check-prefixes=" + ",".join(self.fixes.prefixes), + "-strict-whitespace", + ] + ) + + def check_messages(self, clang_tidy_output): + if self.has_check_messages: + messages_file = self.temp_file_name + ".msg" + write_file(messages_file, clang_tidy_output) + try_run( + [ + "FileCheck", + "-input-file=" + messages_file, + self.input_file_name, + "-check-prefixes=" + ",".join(self.messages.prefixes), + "-implicit-check-not={{warning|error}}:", + ] + ) + + def check_notes(self, clang_tidy_output): + if self.has_check_notes: + notes_file = self.temp_file_name + ".notes" + filtered_output = [ + line + for line in clang_tidy_output.splitlines() + if not ("note: FIX-IT applied" in line) + ] + write_file(notes_file, "\n".join(filtered_output)) + try_run( + [ + "FileCheck", + "-input-file=" + notes_file, + self.input_file_name, + "-check-prefixes=" + ",".join(self.notes.prefixes), + "-implicit-check-not={{note|warning|error}}:", + ] + ) + + def run(self): + self.read_input() + if self.export_fixes is None: + self.get_prefixes() + self.prepare_test_inputs() + clang_tidy_output = self.run_clang_tidy() + if self.expect_no_diagnosis: + self.check_no_diagnosis(clang_tidy_output) + elif self.export_fixes is None: + self.check_fixes() + self.check_messages(clang_tidy_output) + self.check_notes(clang_tidy_output) + + +CPP_STANDARDS = [ + "c++98", + "c++11", + ("c++14", "c++1y"), + ("c++17", "c++1z"), + ("c++20", "c++2a"), + ("c++23", "c++2b"), + ("c++26", "c++2c"), +] +C_STANDARDS = ["c99", ("c11", "c1x"), "c17", ("c23", "c2x"), "c2y"] + + +def expand_std(std): + split_std, or_later, _ = std.partition("-or-later") + + if not or_later: + return [split_std] + + for standard_list in (CPP_STANDARDS, C_STANDARDS): + item = next( + ( + i + for i, v in enumerate(standard_list) + if (split_std in v if isinstance(v, (list, tuple)) else split_std == v) + ), + None, + ) + if item is not None: + return [split_std] + [ + x if isinstance(x, str) else x[0] for x in standard_list[item + 1 :] + ] + return [std] + + +def csv(string): + return string.split(",") + + +def parse_arguments(): + parser = argparse.ArgumentParser( + prog=pathlib.Path(__file__).stem, + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("-expect-clang-tidy-error", action="store_true") + parser.add_argument("-resource-dir") + parser.add_argument("-assume-filename") + parser.add_argument("input_file_name") + parser.add_argument("check_name") + parser.add_argument("temp_file_name") + parser.add_argument( + "-check-suffix", + "-check-suffixes", + default=[""], + type=csv, + help="comma-separated list of FileCheck suffixes", + ) + parser.add_argument( + "-export-fixes", + default=None, + type=str, + metavar="file", + help="A file to export fixes into instead of fixing.", + ) + parser.add_argument( + "-std", + type=csv, + default=["c++11-or-later"], + help="Passed to clang. Special -or-later values are expanded.", + ) + parser.add_argument( + "-plugin", + ) + return parser.parse_known_args() + + +def main(): + args, extra_args = parse_arguments() + + abbreviated_stds = args.std + for abbreviated_std in abbreviated_stds: + for std in expand_std(abbreviated_std): + args.std = std + CheckRunner(args, extra_args).run() + + +if __name__ == "__main__": + main() diff --git a/tests/clang_tidy_plugin/modernize-nlohmann-json-explicit-conversions.cpp b/tests/clang_tidy_plugin/modernize-nlohmann-json-explicit-conversions.cpp new file mode 100644 index 000000000..b672343b0 --- /dev/null +++ b/tests/clang_tidy_plugin/modernize-nlohmann-json-explicit-conversions.cpp @@ -0,0 +1,300 @@ +// SPDX-FileCopyrightText: 2025 Mike Crowe +// SPDX-License-Identifier: MIT + +// The test is limited to C++17 because the version of +// check_clang_tidy.py here would like to test with -std=c++23, but +// Clang 16 doesn't support that. + +// RUN: %check_clang_tidy -std=c++17 %s modernize-nlohmann-json-explicit-conversions %t -- -- -isystem %clang_tidy_headers + +typedef __PTRDIFF_TYPE__ ptrdiff_t; +typedef __SIZE_TYPE__ size_t; + +typedef unsigned __INT16_TYPE__ char16; +typedef unsigned __INT32_TYPE__ char32; + +namespace std { +template +class allocator {}; + +template +class char_traits {}; + +template > +struct basic_string_view; + +template , typename A = allocator> +struct basic_string { + typedef size_t size_type; + typedef basic_string _Type; + basic_string(); + basic_string(const C *p, const A &a = A()); + basic_string(const C *p, size_type count); + basic_string(const C *b, const C *e); + + ~basic_string(); + + const C *c_str() const; + const C *data() const; + + bool empty() const; + size_type size() const; + size_type length() const; + + _Type& append(const C *s); + _Type& append(const C *s, size_type n); + _Type& assign(const C *s); + _Type& assign(const C *s, size_type n); + + int compare(const _Type&) const; + int compare(const C* s) const; + int compare(size_type pos, size_type len, const _Type&) const; + int compare(size_type pos, size_type len, const C* s) const; + template + int compare(size_type pos1, size_type count1, const StringViewLike& t) const; + + size_type find(const _Type& str, size_type pos = 0) const; + size_type find(const C* s, size_type pos = 0) const; + size_type find(const C* s, size_type pos, size_type n) const; + + size_type rfind(const _Type& str, size_type pos = npos) const; + size_type rfind(const C* s, size_type pos, size_type count) const; + size_type rfind(const C* s, size_type pos = npos) const; + size_type rfind(C ch, size_type pos = npos) const; + + _Type& insert(size_type pos, const _Type& str); + _Type& insert(size_type pos, const C* s); + _Type& insert(size_type pos, const C* s, size_type n); + + _Type substr(size_type pos = 0, size_type count = npos) const; + + constexpr bool starts_with(std::basic_string_view sv) const noexcept; + constexpr bool starts_with(C ch) const noexcept; + constexpr bool starts_with(const C* s) const; + + constexpr bool ends_with(std::basic_string_view sv) const noexcept; + constexpr bool ends_with(C ch) const noexcept; + constexpr bool ends_with(const C* s) const; + + _Type& operator[](size_type); + const _Type& operator[](size_type) const; + + _Type& operator+=(const _Type& str); + _Type& operator+=(const C* s); + _Type& operator=(const _Type& str); + _Type& operator=(const C* s); + + static constexpr size_t npos = -1; +}; + +typedef basic_string string; +typedef basic_string wstring; +typedef basic_string u16string; +typedef basic_string u32string; + +template +struct basic_string_view { + typedef size_t size_type; + typedef basic_string_view _Type; + + const C *str; + constexpr basic_string_view(const C* s) : str(s) {} + + const C *data() const; + + bool empty() const; + size_type size() const; + size_type length() const; + + size_type find(_Type v, size_type pos = 0) const; + size_type find(C ch, size_type pos = 0) const; + size_type find(const C* s, size_type pos, size_type count) const; + size_type find(const C* s, size_type pos = 0) const; + + size_type rfind(_Type v, size_type pos = npos) const; + size_type rfind(C ch, size_type pos = npos) const; + size_type rfind(const C* s, size_type pos, size_type count) const; + size_type rfind(const C* s, size_type pos = npos) const; + + constexpr bool starts_with(basic_string_view sv) const noexcept; + constexpr bool starts_with(C ch) const noexcept; + constexpr bool starts_with(const C* s) const; + + constexpr bool ends_with(basic_string_view sv) const noexcept; + constexpr bool ends_with(C ch) const noexcept; + constexpr bool ends_with(const C* s) const; + + constexpr int compare(basic_string_view sv) const noexcept; + + static constexpr size_t npos = -1; +}; + +typedef basic_string_view string_view; +typedef basic_string_view wstring_view; +typedef basic_string_view u16string_view; +typedef basic_string_view u32string_view; + +std::string operator+(const std::string&, const std::string&); +std::string operator+(const std::string&, const char*); +std::string operator+(const char*, const std::string&); + +bool operator==(const std::string&, const std::string&); +bool operator==(const std::string&, const char*); +bool operator==(const char*, const std::string&); + +bool operator==(const std::wstring&, const std::wstring&); +bool operator==(const std::wstring&, const wchar_t*); +bool operator==(const wchar_t*, const std::wstring&); + +bool operator==(const std::string_view&, const std::string_view&); +bool operator==(const std::string_view&, const char*); +bool operator==(const char*, const std::string_view&); + +#if __cplusplus < 202002L +bool operator!=(const std::string&, const std::string&); +bool operator!=(const std::string&, const char*); +bool operator!=(const char*, const std::string&); + +bool operator!=(const std::string_view&, const std::string_view&); +bool operator!=(const std::string_view&, const char*); +bool operator!=(const char*, const std::string_view&); +#endif + +size_t strlen(const char* str); +} + +struct MyStruct { + int i1; + int i2; +}; + +namespace nlohmann +{ + class basic_json + { + public: + template + ValueType get() const + { + return ValueType{}; + } + + // nlohmann::json uses SFINAE to limit the types that can be converted to. + // Rather than do that here, let's just provide the overloads we need + // instead. + operator int() const + { + return get(); + } + + operator double() const + { + return get(); + } + + operator std::string() const + { + return get(); + } + + operator MyStruct() const + { + return get(); + } + + int otherMember() const; + }; + + class iterator + { + public: + basic_json &operator*(); + basic_json *operator->(); + }; + + using json = basic_json; +} + +using nlohmann::json; +using nlohmann::iterator; + +int get_int(json &j) +{ + return j; + // CHECK-MESSAGES: [[@LINE-1]]:10: warning: implicit nlohmann::json conversion to int should be explicit [modernize-nlohmann-json-explicit-conversions] + // CHECK-FIXES: return j.get(); +} + +std::string get_string(json &j) +{ + return j; + // CHECK-MESSAGES: [[@LINE-1]]:10: warning: implicit nlohmann::json conversion to std::string should be explicit [modernize-nlohmann-json-explicit-conversions] + // CHECK-FIXES: return j.get(); +} + +MyStruct get_struct(json &j) +{ + return j; + // CHECK-MESSAGES: [[@LINE-1]]:10: warning: implicit nlohmann::json conversion to MyStruct should be explicit [modernize-nlohmann-json-explicit-conversions] + // CHECK-FIXES: return j.get(); +} + +int get_int_ptr(json *j) +{ + return *j; + // CHECK-MESSAGES: [[@LINE-1]]:10: warning: implicit nlohmann::json conversion to int should be explicit [modernize-nlohmann-json-explicit-conversions] + // CHECK-FIXES: return j->get(); +} + +int get_int_ptr_expr(json *j) +{ + return *(j+1); + // CHECK-MESSAGES: [[@LINE-1]]:10: warning: implicit nlohmann::json conversion to int should be explicit [modernize-nlohmann-json-explicit-conversions] + // CHECK-FIXES: return (j+1)->get(); +} + +int get_int_iterator(iterator i) +{ + return *i; + // CHECK-MESSAGES: [[@LINE-1]]:10: warning: implicit nlohmann::json conversion to int should be explicit [modernize-nlohmann-json-explicit-conversions] + // CHECK-FIXES: return i->get(); +} + +int get_int_fn() +{ + extern json get_json(); + return get_json(); + // CHECK-MESSAGES: [[@LINE-1]]:10: warning: implicit nlohmann::json conversion to int should be explicit [modernize-nlohmann-json-explicit-conversions] + // CHECK-FIXES: return get_json().get(); +} + +double get_double_fn_ref() +{ + extern nlohmann::json &get_json_ref(); + return get_json_ref(); + // CHECK-MESSAGES: [[@LINE-1]]:10: warning: implicit nlohmann::json conversion to double should be explicit [modernize-nlohmann-json-explicit-conversions] + // CHECK-FIXES: return get_json_ref().get(); +} + +std::string get_string_fn_ptr() +{ + extern json *get_json_ptr(); + return *get_json_ptr(); + // CHECK-MESSAGES: [[@LINE-1]]:10: warning: implicit nlohmann::json conversion to std::string should be explicit [modernize-nlohmann-json-explicit-conversions] + // CHECK-FIXES: return get_json_ptr()->get(); +} + +int call_other_member(nlohmann::json &j) +{ + return j.otherMember(); +} + +int call_other_member_ptr(nlohmann::json *j) +{ + return j->otherMember(); +} + +int call_other_member_iterator(iterator i) +{ + return i->otherMember(); +}