├── CMakeLists.txt ├── README.md ├── ast_visitor.hpp ├── constexpr_if.cpp ├── main.cpp ├── rewriter.cpp ├── rewriter.hpp ├── structured_bindings.cpp ├── template_specializer.cpp └── test ├── CMakeLists.txt ├── n3638_return_type_deduction.cpp ├── n3928_static_assert_with_optional_message.cpp └── test_main.cpp /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 3.10 is the version I'm using, but if you're on a lower version feel free to comment out this line and report back results... 2 | cmake_minimum_required(VERSION 3.10) 3 | project(cftf) 4 | 5 | find_package(Clang REQUIRED CONFIG HINTS /usr/lib64/cmake/clang) 6 | find_package(LLVM REQUIRED CONFIG HINTS /usr/lib64/cmake/llvm) 7 | 8 | add_executable(cftf constexpr_if.cpp main.cpp rewriter.cpp structured_bindings.cpp template_specializer.cpp) 9 | target_link_libraries(cftf LLVM clangAST clangBasic clangFrontend clangLex clangTooling clangRewrite stdc++fs) 10 | set_property(TARGET cftf PROPERTY CXX_STANDARD 17) 11 | set_property(TARGET cftf PROPERTY CXX_STANDARD_REQUIRED ON) 12 | 13 | install(TARGETS cftf RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clang From The Future 2 | 3 | CFTF is a source-to-source compiler for C++ developers who want to make use of C++14 and C++17 features on platforms/toolchains that usually don't support doing so. To this end, the tool converts modern C++ language features into their equivalent "old school" versions so that your current toolchain can process it. 4 | 5 | CFTF is intended to be used as a preprocessor for other compilers and hence integrates transparently into your existing build system. When using CMake, this process is very easy to set up. 6 | 7 | In theory, CFTF works with any compiler, although currently only compilers with gcc/clang's CLI interfaces have been tested in practice. Patches for MSVC support or other platforms are very much welcome! 8 | 9 | ## Why? 10 | 11 | A lot of the features added in C++14 and C++17 are purely syntactical sugar, so it always bothered me that we have to wait for a compiler update rather than being able to "just" make our current compiler see through the abstraction. CFTF is my attempt of teaching existing compilers how new language features work. 12 | 13 | There are a number of use cases for this: 14 | 15 | * Early adoption of new standards while waiting for official support from your toolchain vendor 16 | * Porting an existing C++14/17 code base to a toolchain that doesn't receive any vendor updates anymore 17 | * Enabling use of libraries implemented in C++17 such as [hana](https://github.com/boostorg/hana) or [range-v3](https://github.com/ericniebler/range-v3) in a codebase that uses C++11 apart from those libraries 18 | 19 | ## Build and Usage Instructions 20 | 21 | To build CFTF, install libclang 6.0 and then use 22 | 23 | mkdir build 24 | cd build 25 | cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .. 26 | 27 | Then, to compile a CMake-based C++14/17 project with your existing toolchain compiler (e.g. g++), use: 28 | 29 | CXX=/usr/local/bin/cftf cmake -DCMAKE_CXX_FLAGS="-frontend-compiler=/usr/bin/g++" .. 30 | 31 | If the clang compiler executable is not installed on the system, you also need to add libclang's resource directory to `CMAKE_CXX_FLAGS` via `-resource-dir=/usr/lib64/clang/6.0.1` (the actual path depends on yout libclang installation). 32 | 33 | Projects not using CMake need to resort to hacky solutions, currently. One method is to rename your existing compiler executable and put a copy of the `cftf` executable in its old place. To point CFTF to the frontend compiler, set the CFTF_FRONTEND_CXX, e.g. `export CFTF_FRONTEND_CXX=/usr/bin/g++`. 34 | 35 | To make sure CFTF functions correctly, you can try out the `tests/` (using their standalone CMakeLists.txt). 36 | 37 | ## Current Status 38 | 39 | CFTF is ready enough to be tried out "for fun", but it's still mostly a proof-of-concept. I encourage you to try it out if the idea sounds useful to you, but do note it's not ready for use in production currently. I'm hoping with feedback from the community this will soon change, though! 40 | 41 | At the moment, all of the following C++ features will be converted to C++11-compatible code, with support for more features planned in the future: 42 | * Structured bindings 43 | * Constexpr if 44 | * Function return type deduction, e.g. `auto func() { return 5; }` 45 | * Optional static assertion messages, e.g. `static_assert(sizeof(T) > 4)` 46 | * Fold expressions (soon!) 47 | 48 | Furthermore, CFTF can convert parameter pack expansions to C++98-compatible code. 49 | 50 | ## Future 51 | 52 | The current feature list is small compared to the total list of [C++14](https://en.wikipedia.org/wiki/C%2B%2B14) and [C++17](https://en.wikipedia.org/wiki/C%2B%2B17) changes. The set of supported features is intentionally kept small for now until support for them works robustly and correctly in all weird corner cases that might arise. 53 | 54 | That said, once things are rock-stable, support for new features will be added, and I will also explore ways of supporting pre-C++11 targets. 55 | -------------------------------------------------------------------------------- /ast_visitor.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CFTF_AST_VISITOR_HPP 2 | #define CFTF_AST_VISITOR_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace cftf { 8 | 9 | // TODO: This should be moved elsewhere 10 | namespace features { 11 | 12 | inline bool constexpr_if = true; 13 | 14 | } 15 | 16 | class RewriterBase; 17 | 18 | class ASTVisitor : public clang::RecursiveASTVisitor { 19 | using Parent = clang::RecursiveASTVisitor; 20 | 21 | public: 22 | ASTVisitor(clang::ASTContext& context, clang::Rewriter& rewriter_); 23 | 24 | bool VisitDeclStmt(clang::DeclStmt* stmt); 25 | 26 | bool VisitVarDecl(clang::VarDecl* decl); 27 | 28 | bool VisitSizeOfPackExpr(clang::SizeOfPackExpr* expr); 29 | 30 | bool TraversePackExpansionExpr(clang::PackExpansionExpr* expr); 31 | 32 | bool VisitPackExpansionExpr(clang::PackExpansionExpr* expr); 33 | 34 | bool VisitDeclRefExpr(clang::DeclRefExpr* expr); 35 | 36 | bool VisitCXXFoldExpr(clang::CXXFoldExpr* expr); 37 | 38 | bool TraverseCXXFoldExpr(clang::CXXFoldExpr* expr); 39 | 40 | // Used to explicitly specialize function templates which are otherwise specialized implicitly 41 | bool TraverseFunctionTemplateDecl(clang::FunctionTemplateDecl* decl); 42 | 43 | bool VisitFunctionDecl(clang::FunctionDecl* decl); 44 | 45 | bool VisitIfStmt(clang::IfStmt* stmt); 46 | 47 | bool VisitDecompositionDecl(clang::DecompositionDecl* decl); 48 | 49 | bool VisitStaticAssertDecl(clang::StaticAssertDecl* decl); 50 | 51 | bool shouldTraversePostOrder() const; 52 | 53 | bool shouldVisitTemplateInstantiations() const { return true; } 54 | 55 | struct FunctionTemplateInfo { 56 | // The DeclRefExpr are parameter packs referenced in the pack expansion 57 | struct ParamPackExpansionInfo { 58 | clang::PackExpansionExpr* expr; 59 | std::vector referenced_packs; 60 | }; 61 | std::vector param_pack_expansions; 62 | bool in_param_pack_expansion = false; 63 | 64 | // List of all Decls in this template. Used for reverse-lookup in 65 | // implicit specializations to find templated Decls from specialized 66 | // ones. 67 | std::vector decls; 68 | 69 | // Assumes there is only a single one of these. 70 | // Don't use this for ParmVarDecls, since they might be parameter packs 71 | // (i.e. multiple specialized Decls per single templated Decl)! 72 | clang::Decl* FindTemplatedDecl(clang::SourceManager& sm, clang::Decl* specialized) const; 73 | }; 74 | 75 | struct CurrentFunctionInfo { 76 | clang::FunctionDecl* decl; 77 | 78 | FunctionTemplateInfo* template_info; 79 | 80 | struct Parameter { 81 | // decl in the primary function template 82 | clang::ParmVarDecl* templated; 83 | 84 | // Decl and unique name for parameter(s) in a CFTF-specialized 85 | // function template. Note that there may be muliple of these, 86 | // since multiple arguments may be passed for a single variadic 87 | // template parameter 88 | struct SpecializedParameter { 89 | clang::ParmVarDecl* decl; 90 | 91 | // Unique name generated for this argument: Generally, we just 92 | // copy over the templated parameter name into this field, 93 | // however for variadic parameters this would cause all 94 | // arguments generated from a parameter pack to be assigned the 95 | // same name. 96 | std::string unique_name; 97 | }; 98 | 99 | // decl in the specialized function. 100 | // If "templated" is a parameter pack, there may be multiple decls here (or none) 101 | // NOTE: This is populated only if the parameter is named! 102 | std::vector specialized; 103 | }; 104 | 105 | std::vector parameters; 106 | 107 | clang::ParmVarDecl* FindTemplatedParamDecl(clang::ParmVarDecl* specialized) const; 108 | const std::vector& FindSpecializedParamDecls(clang::ParmVarDecl* templated) const; 109 | }; 110 | 111 | 112 | private: 113 | // Gets the string of the contents enclosed by the two SourceLocations extended to the end of the last token 114 | clang::SourceLocation getLocForEndOfToken(clang::SourceLocation end); 115 | 116 | // Gets the string of the contents enclosed by the two SourceLocations extended to the end of the last token 117 | std::string GetClosedStringFor(clang::SourceLocation begin, clang::SourceLocation end); 118 | 119 | clang::ASTContext& context; 120 | std::unique_ptr rewriter; 121 | 122 | bool IsInFullySpecializedFunction() const { 123 | return current_function.has_value(); 124 | } 125 | 126 | std::optional current_function; 127 | 128 | // Only valid while traversing a template function. 129 | // In particular, not valid in implicit specializations 130 | FunctionTemplateInfo* current_function_template = nullptr; 131 | 132 | std::unordered_map function_templates; 133 | 134 | }; 135 | 136 | } // namespace cftf 137 | 138 | #endif // CFTF_AST_VISITOR_HPP 139 | -------------------------------------------------------------------------------- /constexpr_if.cpp: -------------------------------------------------------------------------------- 1 | #include "ast_visitor.hpp" 2 | #include "rewriter.hpp" 3 | 4 | #include 5 | 6 | namespace cftf { 7 | 8 | /** 9 | * Traverses the templated version of a function and tries to find a match for the given if statement. 10 | * This is used for "constexpr if", where clang helpfully strips away untaken branches in specialized 11 | * functions. Looking up the original statement allows us to get the SourceLocation information of 12 | * those branches we need to do our transformations reliably. 13 | */ 14 | class FinderForOriginalIfStmt : public clang::RecursiveASTVisitor { 15 | public: 16 | FinderForOriginalIfStmt(clang::FunctionDecl* func, clang::IfStmt* stmt) : specialized_stmt(stmt) { 17 | // Make sure "func" is indeed a specialization 18 | assert (func->getPrimaryTemplate() != nullptr); 19 | { 20 | TraverseFunctionDecl(func->getPrimaryTemplate()->getTemplatedDecl()); 21 | } 22 | } 23 | 24 | operator clang::IfStmt*() const { 25 | return match; 26 | } 27 | 28 | bool VisitIfStmt(clang::IfStmt* stmt) { 29 | if (specialized_stmt->getLocStart() == stmt->getLocStart()) { 30 | match = stmt; 31 | // Abort traversal for this check 32 | return false; 33 | } 34 | 35 | return true; 36 | } 37 | 38 | bool needs_specialization = false; 39 | 40 | clang::IfStmt* specialized_stmt; 41 | clang::IfStmt* match = nullptr; 42 | }; 43 | 44 | bool ASTVisitor::VisitIfStmt(clang::IfStmt* stmt) { 45 | if (features::constexpr_if && stmt->isConstexpr()) { 46 | // TODO: Only use this if VisitFunctionDecl has ensured we can modify the current function! 47 | } 48 | 49 | if (!IsInFullySpecializedFunction()) { 50 | return true; 51 | } 52 | 53 | clang::Expr* cond = stmt->getCond(); 54 | assert(cond); 55 | 56 | bool result; 57 | bool eval_succeeded = cond->EvaluateAsBooleanCondition(result, context); 58 | // TODO: For newer clang: 59 | // clang::EvalResult result; 60 | // bool eval_succeeded = cond->EvaluateAsConstantExpr(result, clang::Expr::EvaluateForCodeGen, context); 61 | if (!eval_succeeded) { 62 | // This shouldn't have compiled in the first place: "if constexpr" requires a constant expression 63 | std::cerr << "Couldn't evaluate constexpr if condition!" << std::endl; 64 | cond->dump(); 65 | // TODO: This actually does happen currently when we run this in a function template. Just silently ignore it for now hence! (This is covered by our tests) 66 | //assert(false); 67 | return true; 68 | } 69 | 70 | clang::Stmt* branch = result ? stmt->getThen() : stmt->getElse(); 71 | 72 | 73 | assert(current_function); 74 | if (current_function->decl->getPrimaryTemplate()) { 75 | // In function template specializations, clang overly helpfully strips out untaken else-branches right away... 76 | // While that could have been very convenient, it breaks our design since we these branches will still be in the 77 | // StagingRewriter, and now we don't get the SourceLocations of what needs to be removed. 78 | // Hence, we need traverse the entire function we're in and find the "if constexpr" corresponding to the one in the 79 | // specialized function. We detect this correspondence based on stmt->getLocStart(), which hopefully shouldn't 80 | // have changed. 81 | // TODO: This probably breaks down for manually specialized functions 82 | 83 | clang::IfStmt* original_statement = FinderForOriginalIfStmt(current_function->decl, stmt); 84 | assert(original_statement); 85 | branch = result ? original_statement->getThen() : original_statement->getElse(); 86 | stmt = original_statement; 87 | } 88 | 89 | if (branch) { 90 | // Remove all parts of the statement that we statically know aren't needed. 91 | // 92 | // We keep: 93 | // * The conditional (including everything enclosed by the parentheses following "if constexpr") 94 | // * The body of the branch that succeeded (including curly braces {}, if any) 95 | // 96 | // We throw away: 97 | // * "if constexpr", "else", "else if", and the parentheses surrounding their conditions 98 | // (required "if"/"else" keywords might be re-added manually later) 99 | // * Bodies of branches that are not taken (replaced with an empty body {}) 100 | // 101 | // Instead of replacing the entire IfStmt with only what's needed, we smartly remove all unneeded parts individually. 102 | // This ensures the associated SourceLocations stay valid and hence rewrite rules in 103 | // nested nodes apply properly. 104 | // 105 | // NOTE: Naively we might even go as far as removing the conditional entirely; 106 | // however, we do need to carry around any variables defined in the condition 107 | // since they are valid even in else-branches. 108 | 109 | // Initializing expression comes first, then the condition variable declaration, then a plain condition. 110 | // Only getCond() is guaranteed to return a non-null value. 111 | // Test for and assign in the appropriate order. 112 | clang::SourceLocation cond_first = stmt->getCond()->getLocStart(); 113 | if (stmt->getInit()) { 114 | cond_first = stmt->getInit()->getLocStart(); 115 | } else if (stmt->getConditionVariableDeclStmt()) { 116 | cond_first = stmt->getConditionVariableDeclStmt()->getLocStart(); 117 | } 118 | 119 | // Don't need to check for initializing statement here since that precedes the condition anyway 120 | clang::SourceLocation cond_last = stmt->getCond()->getLocEnd(); 121 | if (stmt->getConditionVariableDeclStmt()) { 122 | cond_last = stmt->getConditionVariableDeclStmt()->getLocEnd(); 123 | } 124 | 125 | rewriter->ReplaceTextExcludingEndToken({ stmt->getLocStart(), cond_first }, "if ("); 126 | rewriter->RemoveTextExcludingEndToken({ getLocForEndOfToken(cond_last), branch->getLocStart() }); 127 | if (result) { 128 | rewriter->InsertTextAfter(cond_last, ") "); 129 | } else { 130 | // Add an empty branch body 131 | rewriter->InsertTextAfter(cond_last, ") {} else\n"); 132 | } 133 | 134 | rewriter->RemoveTextIncludingEndToken({ getLocForEndOfToken(branch->getLocEnd()), stmt->getLocEnd() }); 135 | } else { 136 | // Condition was false and no else-branch has been given, so just remove the entire statement 137 | rewriter->RemoveTextIncludingEndToken({ stmt->getLocStart(), stmt->getLocEnd() }); 138 | } 139 | 140 | return true; 141 | } 142 | 143 | } // namespace cftf 144 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "ast_visitor.hpp" 2 | #include "rewriter.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #ifdef __linux__ 20 | namespace llvm { 21 | /** 22 | * http://lists.llvm.org/pipermail/llvm-dev/2017-January/109621.html 23 | * We can't rebuild llvm, but we can define symbol missed in llvm build. 24 | */ 25 | //int DisableABIBreakingChecks = 1; 26 | } 27 | #endif 28 | 29 | namespace ct = clang::tooling; 30 | 31 | namespace cftf { 32 | 33 | static llvm::cl::OptionCategory tool_category("tool options"); 34 | 35 | ASTVisitor::ASTVisitor(clang::ASTContext& context, clang::Rewriter& rewriter_) 36 | : context(context), rewriter(std::unique_ptr(new Rewriter(rewriter_))) { 37 | } 38 | 39 | bool ASTVisitor::VisitCXXFoldExpr(clang::CXXFoldExpr* expr) { 40 | std::cerr << "Visiting CXX fold expression" << std::endl; 41 | std::cerr << " " << std::flush; 42 | auto* pattern = expr->getPattern(); 43 | pattern->dumpColor(); 44 | std::cerr << std::endl; 45 | auto& sm = rewriter->getSourceMgr(); 46 | const auto pattern_base_str = GetClosedStringFor(pattern->getLocStart(), pattern->getLocEnd()); 47 | 48 | // TODO: Support operators: + - * / % ^ & | << >>, all of these with an = at the end; ==, !=, <, >, <=, >=, &&, ||, ",", .*, ->* 49 | using namespace std::literals::string_view_literals; 50 | std::map operators; 51 | operators[clang::BO_Add] = "add"sv; 52 | operators[clang::BO_Sub] = "sub"sv; 53 | operators[clang::BO_Mul] = "mul"sv; 54 | operators[clang::BO_Div] = "div"sv; 55 | operators[clang::BO_Rem] = "mod"sv; 56 | operators[clang::BO_Xor] = "xor"sv; 57 | operators[clang::BO_And] = "and"sv; 58 | operators[clang::BO_Or] = "or"sv; 59 | operators[clang::BO_Shl] = "shl"sv; 60 | operators[clang::BO_Shr] = "shr"sv; 61 | 62 | operators[clang::BO_AddAssign] = "add_assign"sv; 63 | operators[clang::BO_SubAssign] = "sub_assign"sv; 64 | operators[clang::BO_MulAssign] = "mul_assign"sv; 65 | operators[clang::BO_DivAssign] = "div_assign"sv; 66 | operators[clang::BO_RemAssign] = "mod_assign"sv; 67 | operators[clang::BO_XorAssign] = "xor_assign"sv; 68 | operators[clang::BO_AndAssign] = "and_assign"sv; 69 | operators[clang::BO_OrAssign] = "or_assign"sv; 70 | operators[clang::BO_ShlAssign] = "shl_assign"sv; 71 | operators[clang::BO_ShrAssign] = "shr_assign"sv; 72 | 73 | operators[clang::BO_Assign] = "assign"sv; 74 | operators[clang::BO_EQ] = "equals"sv; 75 | operators[clang::BO_NE] = "notequals"sv; 76 | operators[clang::BO_LT] = "less"sv; 77 | operators[clang::BO_GT] = "greater"sv; 78 | operators[clang::BO_LE] = "lessequals"sv; 79 | operators[clang::BO_GE] = "greaterequals"sv; 80 | operators[clang::BO_LAnd] = "land"sv; 81 | operators[clang::BO_LOr] = "lor"sv; 82 | operators[clang::BO_Comma] = "comma"sv; 83 | 84 | auto fold_op = expr->getOperator(); 85 | if (fold_op == clang::BO_PtrMemD || fold_op == clang::BO_PtrMemI) { 86 | // TODO: These might just work, actually... 87 | throw std::runtime_error("Fold expressions on member access operators not supported, yet!"); 88 | } 89 | 90 | auto init_value_str = expr->getInit() ? GetClosedStringFor(expr->getInit()->getLocStart(), expr->getInit()->getLocEnd()) : ""; 91 | 92 | // TODO: What value category should we use for the arguments? 93 | // Currently, assigment operators take lvalue-refs, and anything else copies by value 94 | auto pattern_str = std::string("fold_expr_").append(operators.at(fold_op)); 95 | if (expr->isLeftFold()) { 96 | pattern_str += "_left("; 97 | if (expr->getInit()) { 98 | pattern_str += init_value_str + ", "; 99 | } 100 | } else { 101 | pattern_str += "_right("; 102 | } 103 | pattern_str += pattern_base_str + "..."; 104 | if (expr->isRightFold() && expr->getInit()) { 105 | pattern_str += ", " + init_value_str; 106 | } 107 | pattern_str += ")"; 108 | 109 | std::cerr << " Pattern: \"" << pattern_str << '"' << std::endl; 110 | rewriter->ReplaceTextIncludingEndToken({expr->getLocStart(), expr->getLocEnd()}, pattern_str); 111 | return true; 112 | } 113 | 114 | bool ASTVisitor::TraverseCXXFoldExpr(clang::CXXFoldExpr* expr) { 115 | // We currently can't perform any nested replacements within a fold expressions 116 | // hence, visit this node but none of its children, and instead process those in the next pass 117 | 118 | std::cerr << "Traversing fold expression: " << GetClosedStringFor(expr->getLocStart(), expr->getLocEnd()) << std::endl; 119 | 120 | Parent::WalkUpFromCXXFoldExpr(expr); 121 | 122 | return true; 123 | } 124 | 125 | bool ASTVisitor::VisitStaticAssertDecl(clang::StaticAssertDecl* decl) { 126 | if (decl->getMessage() == nullptr) { 127 | // Add empty assertion message 128 | auto assert_cond = GetClosedStringFor(decl->getAssertExpr()->getLocStart(), decl->getAssertExpr()->getLocEnd()); 129 | auto& sm = rewriter->getSourceMgr(); 130 | 131 | auto new_assert = std::string("static_assert(") + assert_cond + ", \"\")"; 132 | rewriter->ReplaceTextIncludingEndToken({decl->getLocStart(), decl->getLocEnd()}, new_assert); 133 | } 134 | 135 | return true; 136 | } 137 | 138 | bool ASTVisitor::shouldTraversePostOrder() const { 139 | // ACTUALLY, visit top-nodes first; that way, we can withhold further transformations in its child nodes if necessary 140 | return false; 141 | 142 | // Visit leaf-nodes first (so we transform the innermost expressions first) 143 | //return true; 144 | } 145 | 146 | clang::SourceLocation ASTVisitor::getLocForEndOfToken(clang::SourceLocation end) { 147 | return clang::Lexer::getLocForEndOfToken(end, 0, rewriter->getSourceMgr(), {}); 148 | } 149 | 150 | std::string ASTVisitor::GetClosedStringFor(clang::SourceLocation begin, clang::SourceLocation end) { 151 | auto& sm = rewriter->getSourceMgr(); 152 | auto begin_data = sm.getCharacterData(begin); 153 | auto end_data = sm.getCharacterData(getLocForEndOfToken(end)); 154 | return std::string(begin_data, end_data - begin_data); 155 | } 156 | 157 | class ASTConsumer : public clang::ASTConsumer { 158 | public: 159 | ASTConsumer(clang::Rewriter& rewriter) : rewriter(rewriter) {} 160 | 161 | void HandleTranslationUnit(clang::ASTContext& context) override { 162 | ASTVisitor{context, rewriter}.TraverseDecl(context.getTranslationUnitDecl()); 163 | } 164 | 165 | private: 166 | clang::Rewriter& rewriter; 167 | }; 168 | 169 | static std::string GetOutputFilename(llvm::StringRef input_filename) { 170 | auto slash_pos = input_filename.find_last_of('/'); 171 | if (slash_pos == std::string::npos) { 172 | slash_pos = 0; 173 | } else { 174 | ++slash_pos; 175 | } 176 | 177 | // Insert "_cftf_out" before the file extension (or at the end of the filename if there is no file extension), 178 | // and then add ".cpp" 179 | auto period_pos = input_filename.find_last_of('.'); 180 | if (period_pos == std::string::npos) { 181 | period_pos = input_filename.size(); 182 | } 183 | 184 | period_pos = std::max(period_pos, slash_pos); 185 | 186 | // TODO: Prefix the output filename with some unique token for the current compilation flags and working directory 187 | std::string output = std::filesystem::temp_directory_path() / std::string(input_filename.begin() + slash_pos, input_filename.begin() + period_pos); 188 | 189 | output += "_cftf_out"; 190 | std::copy(input_filename.begin() + period_pos, input_filename.end(), std::back_inserter(output)); 191 | output += ".cpp"; 192 | return output; 193 | } 194 | 195 | ct::CompilationDatabase* global_compilation_database = nullptr; 196 | 197 | class FrontendAction : public clang::ASTFrontendAction { 198 | public: 199 | FrontendAction() {} 200 | 201 | void EndSourceFileAction() override { 202 | clang::SourceManager& sm = rewriter.getSourceMgr(); 203 | // TODO: Handle stdin 204 | auto filename = sm.getFileEntryForID(sm.getMainFileID())->getName().data(); 205 | auto commands = global_compilation_database->getCompileCommands(filename); 206 | assert(commands.size() == 1); 207 | auto out_filename = GetOutputFilename(commands[0].Filename); 208 | std::ofstream output(out_filename); 209 | llvm::raw_os_ostream llvm_output_stream(output); 210 | rewriter.getEditBuffer(sm.getMainFileID()).write(llvm_output_stream); 211 | } 212 | 213 | std::unique_ptr CreateASTConsumer(clang::CompilerInstance& ci, clang::StringRef file) override { 214 | rewriter.setSourceMgr(ci.getSourceManager(), ci.getLangOpts()); 215 | return llvm::make_unique(rewriter); 216 | } 217 | 218 | private: 219 | clang::Rewriter rewriter; 220 | }; 221 | 222 | } // namespace cftf 223 | 224 | 225 | 226 | struct ParsedCommandLine { 227 | // Indexes into argv, referring to detected input filenames 228 | std::vector input_filename_arg_indices; 229 | 230 | // Indexes into argv, referring to command line arguments that need to be forwarded to the internal libtooling pass 231 | std::vector input_indexes; 232 | 233 | std::string frontend_compiler; 234 | }; 235 | 236 | 237 | ParsedCommandLine ParseCommandLine(size_t argc, const char* argv[]) { 238 | using namespace std::string_literals; 239 | 240 | // TODO: Strip CFTF-specific options from argument list 241 | // TODO: Add options specific to the CFTF pass to argument list 242 | 243 | // Indexes into argv, referring to detected input filenames (including the executable name) 244 | std::vector input_filename_arg_indices; 245 | 246 | // Indexes into known command line arguments 247 | std::vector input_indexes; 248 | 249 | std::string frontend_compiler; 250 | 251 | for (size_t arg_idx = 1; arg_idx < argc; ++arg_idx) { 252 | auto arg = argv[arg_idx]; 253 | 254 | auto is_cpp_file = [](const char* str) -> bool { 255 | // TODO: This is a very incomplete heuristic: 256 | // * Not everybody uses .cpp/.cxx for C++ files 257 | // * The source language can be overridden by user flags 258 | auto len = std::strlen(str); 259 | if ((len > 3) && (0 == std::strcmp(str + len - 4, ".cpp"))) { 260 | return true; 261 | } 262 | if ((len > 3) && (0 == std::strcmp(str + len - 4, ".cxx"))) { 263 | return true; 264 | } 265 | return false; 266 | }; 267 | 268 | if (std::strcmp(arg, "-") == 0) { 269 | // stdin. TODO. 270 | std::cerr << "Using stdin as input not handled yet" << std::endl; 271 | std::exit(1); 272 | } else if (arg[0] == '-') { 273 | // This argument is some sort of flag. 274 | if (std::strcmp(arg, "-c") == 0) { 275 | // Compile only (no linking) 276 | input_indexes.push_back(arg_idx); 277 | } else if (std::strcmp(arg, "-o") == 0) { 278 | // Output filename 279 | // Needed to generate a suitable filename for the generated, intermediate C++ code 280 | input_indexes.push_back(arg_idx++); 281 | input_indexes.push_back(arg_idx); 282 | } else if (arg[1] == 'D' || arg[1] == 'I' || std::strcmp(arg, "-isystem") == 0) { 283 | // Preprocessor define 284 | input_indexes.push_back(arg_idx); 285 | if (arg[2] == ' ' || std::strcmp(arg, "-isystem") == 0) { 286 | if (arg_idx + 1 == argc) { 287 | std::cerr << "Invalid input: Expected symbol after \"-D\" or \"-isystem\", got end of command line" << std::endl; 288 | std::exit(1); 289 | } 290 | // Include the actual definition, too. 291 | // Note that if a value is provided, it's provided via "=VALUE", i.e. it cannot be space-separated. 292 | input_indexes.push_back(++arg_idx); 293 | } 294 | } else if (arg[1] == 's' || arg[2] == 't' || arg[3] == 'd' || arg[4] == '=') { 295 | // Set C++ language standard version 296 | input_indexes.push_back(arg_idx); 297 | } else if (std::strncmp(arg, "-frontend-compiler=", std::strlen("-frontend-compiler=")) == 0) { 298 | // CTFT-internal option 299 | frontend_compiler = arg + strlen("-frontend-compiler="); 300 | } else if (false) { 301 | // TODO: -i, -isystem, -iquote, -idirafter 302 | // TODO: -stdlib? 303 | } else { 304 | std::cerr << "Ignoring command line option \"" << arg << "\"" << std::endl; 305 | } 306 | } else if (is_cpp_file(arg)) { 307 | // TODO: Does this catch all inputs? Is there a way to specify inputs via non-positional command line arguments? 308 | std::cerr << "Detected input cpp file \"" << arg << "\"" << std::endl; 309 | input_filename_arg_indices.push_back(arg_idx); 310 | } 311 | } 312 | 313 | return { std::move(input_filename_arg_indices), std::move(input_indexes), std::move(frontend_compiler) }; 314 | } 315 | 316 | struct InternalCommandLine { 317 | std::vector args; 318 | std::string internal_storage; 319 | }; 320 | 321 | InternalCommandLine BuildInternalCommandLine(const ParsedCommandLine& parsed_cmdline, const char* argv[]) { 322 | using namespace std::string_literals; 323 | 324 | auto&& [ input_filename_arg_indices, input_indexes, ignored ] = parsed_cmdline; 325 | 326 | // Build a restricted command line that only includes all input files 327 | InternalCommandLine internal_command_line; 328 | std::vector& internal_argv = internal_command_line.args; 329 | 330 | internal_command_line.internal_storage.reserve(1000); // TODO: HACK!! 331 | std::transform(input_indexes.begin(), input_indexes.end(), std::back_inserter(internal_argv), 332 | [&argv, &storage=internal_command_line.internal_storage](size_t idx) { 333 | const char* ptr = &*storage.end(); 334 | storage += argv[idx]; 335 | storage += '\0'; 336 | return ptr; 337 | }); 338 | std::transform(input_filename_arg_indices.begin(), input_filename_arg_indices.end(), std::back_inserter(internal_argv), [argv](size_t idx) { return argv[idx]; }); 339 | return internal_command_line; 340 | } 341 | 342 | static std::string GetClangResourceDirectory() { 343 | char resource_dir[PATH_MAX]; 344 | std::FILE* stdout = popen("clang -print-resource-dir", "r"); 345 | if (!stdout) { 346 | std::cerr << "popen failed, falling back to user-specified resource directory for libclang" << std::endl; 347 | return ""; 348 | } 349 | 350 | auto* resource_line = std::fgets(resource_dir, sizeof(resource_dir), stdout); 351 | std::fclose(stdout); 352 | if (!resource_line) { 353 | std::cerr << "Error: Clang couldn't find its own resource-directory?" << std::endl; 354 | return ""; 355 | } 356 | 357 | // Strip new-line: Clang should always print this in normal operation 358 | std::string ret = resource_line; 359 | assert(ret.back() == '\n'); 360 | ret.pop_back(); 361 | return ret; 362 | } 363 | 364 | class CompilationDatabase : public ct::CompilationDatabase { 365 | std::vector infiles; 366 | std::vector commands; 367 | 368 | public: 369 | CompilationDatabase(const ParsedCommandLine& parsed_cmdline, const InternalCommandLine& internal_cmdline, const char* argv[]) { 370 | assert(parsed_cmdline.input_filename_arg_indices.size() <= 1); 371 | 372 | if (!parsed_cmdline.input_filename_arg_indices.empty()) { 373 | std::transform(parsed_cmdline.input_filename_arg_indices.begin(), parsed_cmdline.input_filename_arg_indices.end(), std::back_inserter(infiles), 374 | [argv](size_t index) { 375 | return argv[index]; 376 | }); 377 | 378 | ct::CompileCommand cmd; 379 | cmd.Directory = "."; // Current working directory 380 | cmd.Filename = argv[parsed_cmdline.input_filename_arg_indices[0]]; 381 | 382 | // The first argument is always skipped over, since it's just the executable name. We add it here so libtooling doesn't skip over data that's actually important 383 | cmd.CommandLine.push_back("cftf"); 384 | std::copy(internal_cmdline.args.begin(), internal_cmdline.args.end(), std::back_inserter(cmd.CommandLine)); 385 | 386 | // Override the resource-directory, which defaults to a path 387 | // relative to the current working directory. This is used to 388 | // locate standard library headers though, so we really want to 389 | // use the resource directory of the actual toolchain instead 390 | // TODO: Only specify this when not already provided by the user 391 | cmd.CommandLine.push_back("-resource-dir=" + GetClangResourceDirectory()); 392 | 393 | if (cmd.Filename == "-") { 394 | std::cerr << "stdin not supported, yet" << std::endl; 395 | std::exit(1); 396 | } else { 397 | cmd.Output = cftf::GetOutputFilename(cmd.Filename); 398 | } 399 | 400 | commands.emplace_back(cmd); 401 | } 402 | } 403 | 404 | std::vector getCompileCommands(llvm::StringRef) const override { 405 | // TODO: Take the given path into consideration 406 | return commands; 407 | } 408 | 409 | std::vector getAllFiles() const override { 410 | return infiles; 411 | } 412 | }; 413 | 414 | 415 | int main(int argc, const char* argv[]){ 416 | auto parsed_cmdline = ParseCommandLine(static_cast(argc), argv); 417 | auto internal_argv = BuildInternalCommandLine(parsed_cmdline, argv); 418 | 419 | CompilationDatabase compilation_database(parsed_cmdline, internal_argv, argv); 420 | cftf::global_compilation_database = &compilation_database; 421 | 422 | // Run FrontendAction on each input file 423 | for (auto& file : compilation_database.getAllFiles()) { 424 | std::cerr << "Processing file " << file << std::endl; 425 | 426 | for (auto& cmd : compilation_database.getCompileCommands(file)) { 427 | std::cerr << " Directory: " << cmd.Directory << std::endl; 428 | std::cerr << " Command: "; 429 | for (auto& cmd2 : cmd.CommandLine) { 430 | std::cerr << cmd2 << " "; 431 | } 432 | std::cerr << std::endl; 433 | std::cerr << " Output: " << cmd.Output << std::endl; 434 | } 435 | std::cerr << std::endl; 436 | 437 | ct::ClangTool tool(compilation_database, file); 438 | // TODO: Use a custom DiagnosticsConsumer to silence the redundant warning output 439 | int result = tool.run(ct::newFrontendActionFactory().get()); 440 | if (result != 0) { 441 | std::exit(1); 442 | } 443 | } 444 | 445 | const char* frontend_command = !parsed_cmdline.frontend_compiler.empty() ? parsed_cmdline.frontend_compiler.c_str() : std::getenv("CFTF_FRONTEND_CXX"); 446 | if (!frontend_command || frontend_command[0] == 0) { 447 | std::cerr << "Error: -frontend-compiler not set, nor was CFTF_FRONTEND_CXX set" << std::endl; 448 | exit(1); 449 | } 450 | 451 | // Replace original input filenames with the corresponding cftf output 452 | std::string modified_cmdline = frontend_command; 453 | for (size_t arg_idx = 1; arg_idx < static_cast(argc); ++arg_idx) { 454 | using namespace std::literals::string_literals; 455 | auto arg = argv[arg_idx]; 456 | 457 | if (parsed_cmdline.input_filename_arg_indices.end() != std::find(parsed_cmdline.input_filename_arg_indices.begin(), parsed_cmdline.input_filename_arg_indices.end(), arg_idx)) { 458 | auto compile_commands = compilation_database.getCompileCommands(arg); 459 | assert(!compile_commands.empty()); 460 | if (compile_commands.size() > 1) { 461 | std::cerr << "Compiling the same file multiple times is not supported yet. Please raise a bug report if you run into this issue" << std::endl; 462 | std::exit(1); 463 | } 464 | auto temp_output_filename = compile_commands[0].Output; 465 | std::cerr << "Replacing presumable input argument \"" << arg << "\" with \"" << temp_output_filename << "\"" << std::endl; 466 | // TODO: Wrap filename in quotes! 467 | modified_cmdline += " "s + temp_output_filename; 468 | 469 | // TODO: If "-o" has not been supplied, explicitly add it here. 470 | // This is needed because the default filename chosen depends 471 | // on the input filename, but since we replace the original 472 | // input filename with our intermediate output file, the 473 | // final output will be named differently unless we 474 | // explicitly specifiy it 475 | } else if (std::strncmp(arg, "-frontend-compiler=", std::strlen("-frontend-compiler=")) == 0) { 476 | // CFTF-specific argument => silently drop it from the command line 477 | } else if (/* DISABLES CODE */ (false) && std::strncmp(arg, "-std=", std::strlen("-std=")) == 0) { 478 | // TODO: Should downgrade the C++ version requirements from gnu++17/c++17 to 14 or 11 479 | } else { 480 | // Other argument; just copy this to the new command line 481 | // TODO: Wrap arguments in quotes or escape them! 482 | modified_cmdline += " "s + arg; 483 | } 484 | } 485 | 486 | // Add file path to the include directories to make ""-includes work 487 | // TODO: Instead of doing this, we could just rewrite #include statements for absolute file paths 488 | if (!parsed_cmdline.input_filename_arg_indices.empty()) { 489 | // TODO: This needs to be done for every input file, so it won't work when compiling multiple source files stored in different folders... 490 | assert(parsed_cmdline.input_filename_arg_indices.size() == 1); 491 | auto path = std::filesystem::absolute(argv[parsed_cmdline.input_filename_arg_indices[0]]).parent_path(); 492 | modified_cmdline += " -I\"" + path.string() + "\""; 493 | } 494 | 495 | // Trigger proper compilation 496 | std::cerr << "Invoking \"" << modified_cmdline << "\"" << std::endl; 497 | std::system(modified_cmdline.c_str()); 498 | } 499 | 500 | -------------------------------------------------------------------------------- /rewriter.cpp: -------------------------------------------------------------------------------- 1 | #include "rewriter.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace cftf { 7 | 8 | bool RewriterBase::InsertTextAfter(clang::SourceLocation loc, llvm::StringRef new_str) { 9 | loc = clang::Lexer::getLocForEndOfToken(loc, 0, getSourceMgr(), {}); 10 | return ReplaceTextExcludingEndToken({loc, loc}, new_str); 11 | } 12 | 13 | bool RewriterBase::RemoveTextIncludingEndToken(clang::SourceRange range) { 14 | return ReplaceTextIncludingEndToken(range, ""); 15 | } 16 | 17 | bool RewriterBase::RemoveTextExcludingEndToken(clang::SourceRange range) { 18 | return ReplaceTextExcludingEndToken(range, ""); 19 | } 20 | 21 | bool Rewriter::ReplaceTextIncludingEndToken(clang::SourceRange range, llvm::StringRef new_str) { 22 | return rewriter.ReplaceText(range, new_str); 23 | } 24 | 25 | bool Rewriter::ReplaceTextExcludingEndToken(clang::SourceRange range, llvm::StringRef ref) { 26 | // It seems that clang::Rewriter provides no way of replacing contents up to a given location without also removing the token at that location, 27 | // so just insert the content before and then remove the previous range. 28 | 29 | if (range.getBegin() != range.getEnd()) { 30 | if (rewriter.RemoveText(clang::CharSourceRange::getCharRange(range))) { 31 | return true; 32 | } 33 | } 34 | 35 | return rewriter.InsertTextBefore(range.getBegin(), ref); 36 | } 37 | 38 | clang::SourceManager& Rewriter::getSourceMgr() { 39 | return rewriter.getSourceMgr(); 40 | } 41 | 42 | } // namespace cftf 43 | -------------------------------------------------------------------------------- /rewriter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CFTF_REWRITER_HPP 2 | #define CFTF_REWRITER_HPP 3 | 4 | namespace clang { 5 | class Rewriter; 6 | class SourceLocation; 7 | class SourceRange; 8 | class SourceManager; 9 | } 10 | 11 | namespace llvm { 12 | class StringRef; 13 | } 14 | 15 | namespace cftf { 16 | 17 | /** 18 | * Abstract interface akin to clang::Rewriter. 19 | * This allows us to do more sophisticated source manipulation than through Rewriter alone. 20 | */ 21 | class RewriterBase { 22 | public: 23 | virtual ~RewriterBase() = default; 24 | virtual bool ReplaceTextIncludingEndToken(clang::SourceRange, llvm::StringRef) = 0; 25 | virtual bool ReplaceTextExcludingEndToken(clang::SourceRange, llvm::StringRef) = 0; 26 | bool InsertTextAfter(clang::SourceLocation, llvm::StringRef); 27 | bool RemoveTextIncludingEndToken(clang::SourceRange); 28 | bool RemoveTextExcludingEndToken(clang::SourceRange); 29 | virtual clang::SourceManager& getSourceMgr() = 0; 30 | }; 31 | 32 | /** 33 | * clang::Rewriter based Rewriter that directly operates on the contents of an input source file 34 | */ 35 | class Rewriter final : public RewriterBase { 36 | public: 37 | Rewriter(clang::Rewriter& rewriter) : rewriter(rewriter) {} 38 | 39 | private: 40 | bool ReplaceTextIncludingEndToken(clang::SourceRange, llvm::StringRef) override; 41 | bool ReplaceTextExcludingEndToken(clang::SourceRange, llvm::StringRef) override; 42 | 43 | clang::SourceManager& getSourceMgr() override; 44 | 45 | clang::Rewriter& rewriter; 46 | }; 47 | 48 | } // namespace cftf 49 | 50 | #endif // CFTF_REWRITER_HPP 51 | -------------------------------------------------------------------------------- /structured_bindings.cpp: -------------------------------------------------------------------------------- 1 | #include "ast_visitor.hpp" 2 | #include "rewriter.hpp" 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace cftf { 9 | 10 | // TODO: Should this operate with QualTypes instead? 11 | static bool TypeHasStdTupleSizeSpecialization(clang::ASTContext& context, const clang::Type* type) { 12 | auto tu_decl = context.getTranslationUnitDecl(); 13 | auto& id = context.Idents.get("std"); 14 | auto std_id = context.DeclarationNames.getIdentifier(&id); 15 | auto lookup_result = tu_decl->lookup(std_id); 16 | assert(lookup_result.size() < 2); 17 | if (lookup_result.size() == 0) { 18 | // No standard library headers included 19 | // => std::tuple_size is not available 20 | return false; 21 | } 22 | 23 | clang::NamespaceDecl* std_decl = clang::dyn_cast(lookup_result.front()); 24 | auto tuple_size_lookup_result = std_decl->lookup(&context.Idents.get("tuple_size")); 25 | assert(tuple_size_lookup_result.size() < 2); 26 | if (tuple_size_lookup_result.size() == 0) { 27 | // is not included => std::tuple_size is not available 28 | return false; 29 | } 30 | 31 | auto tuple_size_decl = clang::dyn_cast(tuple_size_lookup_result.front()); 32 | assert(tuple_size_decl); 33 | 34 | std::cerr << "Checking if any std::tuple_size specializations match" << std::endl; 35 | 36 | auto specs = tuple_size_decl->specializations(); 37 | auto spec = std::find_if(std::begin(specs), std::end(specs), 38 | [type](clang::ClassTemplateSpecializationDecl* spec) { 39 | auto&& template_args = spec->getTemplateArgs(); 40 | assert(template_args.size() == 1); 41 | bool match = (template_args[0].getAsType().getTypePtrOrNull() == type->getAs()); 42 | // NOTE: tuple_size is only considered when 43 | // a proper definition is provided 44 | // (plain forward declarations will 45 | // not have any effect) 46 | if (match && !spec->isCompleteDefinition()) { 47 | std::cerr << "Skipping incomplete specialization" << std::endl; 48 | return false; 49 | } 50 | return match; 51 | }); 52 | return (spec != std::end(specs)); 53 | } 54 | 55 | // TODO: Support bit fields. I guess we could just create a local struct type to define a bitfield of the given size? 56 | bool ASTVisitor::VisitDecompositionDecl(clang::DecompositionDecl* decl) { 57 | auto init_expr = decl->getInit(); 58 | assert(init_expr); 59 | auto unqualified_type = init_expr->getType().getTypePtrOrNull(); 60 | assert(unqualified_type); 61 | 62 | std::string temp_name; 63 | for (auto* binding : decl->bindings()) { 64 | if (!temp_name.empty()) { 65 | temp_name += '_'; 66 | } 67 | temp_name += binding->getNameAsString(); 68 | } 69 | temp_name = "cftf_binding_group_" + temp_name; 70 | 71 | auto rewritten_decl = "/*" + GetClosedStringFor(decl->getLocStart(), decl->getLocEnd()) + "*/\n"; 72 | rewritten_decl += "auto " + temp_name + " = " + GetClosedStringFor(decl->getInit()->getLocStart(), decl->getInit()->getLocEnd()) + ";\n"; 73 | 74 | if (unqualified_type->isArrayType()) { 75 | // TODO: For auto, create a new array as a copy of the reference one 76 | // TODO: Decompose array elements; decomposed type is "add_reference_t" 77 | // TODO: Assign decomposed elements to "referenced_array[i]" 78 | std::cerr << "Decomposing array" << std::endl; 79 | // TODO: Implement. 80 | return true; 81 | } else if (TypeHasStdTupleSizeSpecialization(context, unqualified_type)) { 82 | std::cerr << "Decomposing via get<>" << std::endl; 83 | 84 | // TODO: The decomposed types are references to 85 | // "std::tuple_element::type", but we still need to decide 86 | // what kind of reference. For now, we just use "auto(&&)" 87 | // everywhere, but that may not be sufficient in the future. In 88 | // some cases, the bindings shouldn't be C++-references at all, 89 | // but instead should act like transparent references (the type 90 | // of which behaves like a value) 91 | 92 | auto&& bindings = decl->bindings(); 93 | for (auto binding_it = bindings.begin(); binding_it != bindings.end(); ++binding_it) { 94 | auto* binding = *binding_it; 95 | auto index = std::distance(bindings.begin(), binding_it); 96 | auto* var = binding->getHoldingVar(); 97 | 98 | auto type_string = clang::QualType::getAsString(binding->getType().getSplitDesugaredType(), clang::PrintingPolicy{{}}); 99 | 100 | // TODO: Does a plain "get" properly ADL-match all 101 | // cases here? Things to consider: 102 | // * std::get 103 | // * Free function get(Object) 104 | // * Member function get() 105 | // * Friend member function get(Object) 106 | rewritten_decl += type_string + " " + var->getNameAsString() + " = get<" + std::to_string(index) + ">(" + temp_name + ");\n"; 107 | } 108 | } else { 109 | // Decomposed types are references to the respective data members 110 | std::cerr << "Decomposing struct" << std::endl; 111 | 112 | auto bindings = decl->bindings(); 113 | auto init_expr_record = init_expr->getType().getTypePtr()->getAsCXXRecordDecl(); 114 | if (init_expr_record) { 115 | init_expr->getType().dump(); 116 | 117 | struct FindNonEmptyBase { 118 | clang::CXXRecordDecl* operator()(clang::CXXBaseSpecifier& base) { 119 | auto type = base.getType().getTypePtr(); 120 | assert(type); 121 | auto record = type->getAsCXXRecordDecl(); 122 | assert(record); 123 | std::cerr << "Checking base "; 124 | type->dump(); 125 | std::cerr << std::endl; 126 | return (*this)(record); 127 | } 128 | 129 | clang::CXXRecordDecl* operator()(clang::CXXRecordDecl* record) { 130 | auto&& fields = record->fields(); 131 | if (fields.begin() != fields.end()) { 132 | std::cerr << "Done." << std::endl; 133 | return record; 134 | } 135 | for (auto&& base : record->bases()) { 136 | auto match = (*this)(base); 137 | if (match) { 138 | return match; 139 | } 140 | } 141 | 142 | return nullptr; 143 | } 144 | }; 145 | 146 | // Current structure may not have any direct data members, in which case they must have been inherited from a parent 147 | auto non_empty_base = FindNonEmptyBase{}(init_expr_record); 148 | assert(non_empty_base); 149 | auto fields = non_empty_base->fields(); 150 | 151 | auto fields_it = fields.begin(); 152 | for (size_t i = 0; i < bindings.size(); ++i) { 153 | assert(fields_it != fields.end()); 154 | assert(fields_it->getFieldIndex() == i); 155 | auto type_string = clang::QualType::getAsString(fields_it->getType().getSplitDesugaredType(), clang::PrintingPolicy{{}}); 156 | rewritten_decl += type_string + " " + bindings[i]->getNameAsString() + " = " + temp_name + "." + fields_it->getNameAsString() + ";\n"; 157 | ++fields_it; 158 | } 159 | assert(fields_it == fields.end()); 160 | 161 | rewriter->ReplaceTextIncludingEndToken(decl->getSourceRange(), rewritten_decl); 162 | } else { 163 | // This will happen in dependent contexts. TODO! 164 | std::cerr << "Right-hand side is not a CXXRecordDecl; not sure what to do with this..." << std::endl; 165 | } 166 | } 167 | 168 | rewriter->ReplaceTextIncludingEndToken(decl->getSourceRange(), rewritten_decl); 169 | 170 | return true; 171 | } 172 | 173 | } // namespace cftf 174 | -------------------------------------------------------------------------------- /template_specializer.cpp: -------------------------------------------------------------------------------- 1 | #include "ast_visitor.hpp" 2 | #include "rewriter.hpp" 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace cftf { 12 | 13 | static auto SourceRangeLength(clang::SourceManager& sm, clang::SourceRange range) { 14 | auto begin_data = sm.getCharacterData(range.getBegin()); 15 | auto end_data = sm.getCharacterData(range.getEnd()); 16 | return (end_data - begin_data); 17 | } 18 | 19 | static auto SourceRangeToString(clang::SourceManager& sm, clang::SourceRange range) { 20 | auto begin_data = sm.getCharacterData(range.getBegin()); 21 | return std::string(begin_data, SourceRangeLength(sm, range)); 22 | } 23 | 24 | static bool IsSubRange(clang::SourceManager& sm, clang::SourceRange inner, clang::SourceRange outer) { 25 | return (sm.isPointWithin(inner.getBegin(), outer.getBegin(), outer.getEnd()) && 26 | sm.isPointWithin(inner.getEnd(), outer.getBegin(), outer.getEnd())); 27 | } 28 | 29 | /** 30 | * Rewriter that takes a copy of the given range and performs manipulations on 31 | * it based on original SourceLocations but without modifying the original text 32 | * 33 | * In contrast to clang::Rewriter this class allows for "hierarchical" 34 | * rewriting, where multiple rewrite rules might operate on nested parts 35 | * of a single expressions (e.g. a parameter pack expansion including 36 | * binary literals). 37 | * 38 | * This rewriter also supports copy-operations ("instances") such that 39 | * consecutive edits of common subexpressions are visible in all instances 40 | * while allowing to do individual edits as well. 41 | * 42 | * For example, when specializing the expression 43 | * "my_function((0b0010 * ts)...)" 44 | * for ts=<5, 10, 'c'>, the following operations may be performed: 45 | * a) The parameter pack expansion rules creates three instances of the 46 | * subexpression "(0b1000 * ts)..." 47 | * b) The separator ", " is added to the first two instances 48 | * c) The DeclRefExpr matcher replaces "ts" in each instance with a unique 49 | * numbered identifier (ts1, ts2, ts3) 50 | * d) The IntegerLiteral matcher replaces 0b0010 with 2 in all instances 51 | * The difference between (c) and (d) is that (c) edits each instance 52 | * separately whereas in (d), the rewriter class automatically distributes the 53 | * edit across all instances. 54 | * The resulting generated source code is 55 | * "my_function((2 * ts1), (2 * ts2), (2 * ts3))". 56 | */ 57 | class HierarchicalRewriter final : public RewriterBase { 58 | struct SourceNode { 59 | // Half-open interval of source contained by this node. Beginning is included, end is not. 60 | clang::SourceRange range; 61 | 62 | // true if this is original, unmodified source data; false otherwise. 63 | // if false, the contents of this child may not be split up during rewrites. In other words, 64 | // the child must either be left untouched or replaced as a whole 65 | bool rewriteable; 66 | 67 | bool IsLeaf() const { 68 | return std::holds_alternative(data); 69 | } 70 | 71 | /// Child nodes or node content 72 | std::variant, std::string> data; 73 | 74 | std::vector& GetChildren() { 75 | return std::get>(data); 76 | } 77 | 78 | const std::vector& GetChildren() const { 79 | return std::get>(data); 80 | } 81 | 82 | std::string& GetContent() { 83 | return std::get(data); 84 | } 85 | 86 | std::string Concatenate() const { 87 | if (IsLeaf()) { 88 | return std::get(data); 89 | } else { 90 | return std::accumulate(GetChildren().cbegin(), GetChildren().cend(), std::string{}, 91 | [](const std::string& str, const SourceNode& node) { 92 | return str + node.Concatenate(); 93 | }); 94 | } 95 | } 96 | 97 | size_t node_id = 0; 98 | size_t instance_id = 0; 99 | 100 | static constexpr size_t all_instances = std::numeric_limits::max(); 101 | 102 | bool operator==(const SourceNode& oth) const { 103 | return range == oth.range && rewriteable == oth.rewriteable && data == oth.data && instance_id == oth.instance_id; 104 | } 105 | }; 106 | 107 | size_t running_node_id = 1000; 108 | 109 | public: 110 | struct InstanceHandle { 111 | private: 112 | InstanceHandle(SourceNode& node, SourceNode& parent) : node_id(node.node_id), parent_id(parent.node_id) {} 113 | InstanceHandle(size_t node_id, size_t parent_id) : node_id(node_id), parent_id(parent_id) {} 114 | 115 | // TODO: To support nested instances, these will likely need to be std::vectors of nodes in the future 116 | size_t node_id; 117 | size_t parent_id; 118 | 119 | friend class HierarchicalRewriter; 120 | }; 121 | 122 | HierarchicalRewriter(clang::SourceManager& sm, clang::SourceRange range) 123 | : sm(sm), root{range, true, SourceRangeToString(sm, range)} { 124 | } 125 | 126 | std::string GetContents() const { 127 | return root.Concatenate(); 128 | } 129 | 130 | InstanceHandle MakeInstanceHandle(clang::SourceRange subrange) { 131 | return MakeInstanceHandle(root, subrange); 132 | } 133 | 134 | SourceNode& FindNode(SourceNode& parent, size_t id) { 135 | auto ptr = FindNodeHelper(parent, id); 136 | assert(ptr); 137 | return *ptr; 138 | } 139 | 140 | InstanceHandle CreateNewInstance(const InstanceHandle& base_instance) { 141 | auto& parent = FindNode(root, base_instance.parent_id); 142 | auto node = FindNode(parent, base_instance.node_id); // Copy intended 143 | auto& children = parent.GetChildren(); 144 | auto node_it = std::find(children.begin(), children.end(), node); 145 | 146 | // Find the most recent instance of this node, and insert a new instance after it 147 | while (std::next(node_it) != children.end() && node_it->instance_id < std::next(node_it)->instance_id) { 148 | ++node_it; 149 | } 150 | 151 | auto new_instance_id = 1 + node_it->instance_id; 152 | auto new_instance = children.insert(node_it + 1, node); 153 | new_instance->instance_id = new_instance_id; 154 | new_instance->node_id = ++running_node_id; 155 | return InstanceHandle { new_instance->node_id, base_instance.parent_id }; 156 | } 157 | 158 | bool ReplaceTextExcludingEndToken(InstanceHandle& instance, clang::SourceRange replaced_range, llvm::StringRef new_str) { 159 | return ReplaceTextExcludingEndToken(FindNode(root, instance.node_id), replaced_range, new_str); 160 | } 161 | 162 | private: 163 | SourceNode* FindNodeHelper(SourceNode& parent, size_t id) { 164 | if (parent.node_id == id) { 165 | return &parent; 166 | } 167 | 168 | if (parent.IsLeaf()) { 169 | return nullptr; 170 | } else { 171 | for (auto& child : parent.GetChildren()) { 172 | auto ptr = FindNodeHelper(child, id); 173 | if (ptr) return ptr; 174 | } 175 | return nullptr; 176 | } 177 | } 178 | 179 | // Returns a reference to the inner node 180 | SourceNode& SplitNodeAt(SourceNode& node, clang::SourceRange subrange) { 181 | std::vector children; 182 | size_t index_for_inner_node = 0; 183 | 184 | auto left_range = clang::SourceRange { node.range.getBegin(), subrange.getBegin() }; 185 | if (left_range.getBegin() != left_range.getEnd()) { 186 | children.emplace_back(SourceNode { left_range, true, GetHalfOpenStringFor(left_range), ++running_node_id }); 187 | index_for_inner_node = 1; 188 | } 189 | 190 | children.emplace_back(SourceNode { subrange, true, GetHalfOpenStringFor(subrange), ++running_node_id }); 191 | 192 | auto right_range = clang::SourceRange { subrange.getEnd(), node.range.getEnd() }; 193 | if (right_range.getBegin() != right_range.getEnd()) { 194 | children.emplace_back(SourceNode { right_range, true, GetHalfOpenStringFor(right_range), ++running_node_id }); 195 | } 196 | 197 | // If the node wasn't wholly covered, we should have more than one child now 198 | assert(children.size() > 1); 199 | 200 | node.data = std::move(children); 201 | 202 | return node.GetChildren()[index_for_inner_node]; 203 | } 204 | 205 | bool ReplaceTextIncludingEndToken(clang::SourceRange subrange, llvm::StringRef new_str) override { 206 | clang::SourceRange extended_range {subrange.getBegin(), clang::Lexer::getLocForEndOfToken(subrange.getEnd(), 0, sm, {}) }; 207 | return ReplaceTextExcludingEndToken(extended_range, new_str); 208 | } 209 | 210 | // TODO: Remove this 211 | public: 212 | std::string GetHalfOpenStringFor(clang::SourceRange range) { 213 | auto begin_data = sm.getCharacterData(range.getBegin()); 214 | auto end_data = sm.getCharacterData(range.getEnd()); 215 | return std::string(begin_data, end_data - begin_data); 216 | } 217 | bool ReplaceTextExcludingEndToken(SourceNode& node, clang::SourceRange replaced_range, llvm::StringRef new_str) { 218 | if (node.IsLeaf()) { 219 | if (node.range == replaced_range) { 220 | // Node coincides with replaced range, so just replace it directly 221 | node.rewriteable = false; 222 | node.data = new_str; 223 | } else { 224 | // Split this leaf into (up to) 3 children and replace the inner part 225 | auto& inner_node = SplitNodeAt(node, replaced_range); 226 | inner_node.rewriteable = false; 227 | inner_node.data = new_str; 228 | } 229 | } else { 230 | // Recurse into the smallest child that wholly covers replaced_range 231 | auto& children = node.GetChildren(); 232 | auto child_it = std::find_if(children.begin(), children.end(), 233 | [&](SourceNode& child) { 234 | return IsSubRange(sm, replaced_range, child.range); 235 | }); 236 | if (child_it == children.end()) { 237 | // Not implemented, currently. Not sure if we need this? 238 | // No child contains the entire SourceRange found, so we'll replace all the children in this node that are covered. 239 | auto child_is_fully_covered = [&](SourceNode& child) { 240 | bool fully_covered = IsSubRange(sm, child.range, replaced_range); 241 | return fully_covered; 242 | }; 243 | 244 | auto first_child = std::find_if(children.begin(), children.end(), child_is_fully_covered); 245 | 246 | // If replacement string is non-empty, replace the first matching child in-place and drop all other children. 247 | // Otherwise, drop all matching children 248 | auto first_child_to_remove = new_str.empty() ? first_child : (first_child + 1); 249 | auto children_to_remove = std::remove_if(first_child_to_remove, children.end(), child_is_fully_covered); 250 | 251 | if (!new_str.empty()) { 252 | clang::SourceLocation range_end = (children_to_remove != children.end()) ? children.back().range.getEnd() : first_child->range.getEnd(); 253 | *first_child = SourceNode { { first_child->range.getBegin(), range_end }, false, new_str, ++running_node_id }; 254 | } 255 | 256 | children.erase(children_to_remove, children.end()); 257 | } else { 258 | // Recurse into the child (each instance separately) 259 | size_t instance = 0; 260 | auto instance_it = child_it; 261 | while (instance_it != children.end() && instance_it->instance_id == instance) { 262 | ReplaceTextExcludingEndToken(*child_it, replaced_range, new_str); 263 | ++instance; 264 | ++instance_it; 265 | } 266 | 267 | // We should have reached either the end of children or the start of another block of instances 268 | assert(instance_it == children.end() || instance_it->instance_id == 0); 269 | } 270 | } 271 | 272 | // Report success 273 | return false; 274 | } 275 | bool ReplaceTextExcludingEndToken(clang::SourceRange subrange, llvm::StringRef new_str) override { 276 | return ReplaceTextExcludingEndToken(root, subrange, new_str); 277 | } 278 | 279 | InstanceHandle MakeInstanceHandle(SourceNode& parent, clang::SourceRange subrange) { 280 | if (parent.IsLeaf()) { 281 | if (parent.range == subrange) { 282 | // Bleh, generate a nested child, just so we don't need to look up the proper parent ourselves now... 283 | SourceNode child = std::move(parent); 284 | parent = SourceNode { child.range, child.rewriteable, std::vector(1, child), ++running_node_id }; 285 | return InstanceHandle { parent.GetChildren()[0], parent }; 286 | } else { 287 | auto& inner_node = SplitNodeAt(parent, subrange); 288 | return InstanceHandle { inner_node, parent }; 289 | } 290 | } else { 291 | auto& children = parent.GetChildren(); 292 | auto child_it = std::find_if(children.begin(), children.end(), 293 | [&](SourceNode& child) { 294 | return IsSubRange(sm, subrange, child.range); 295 | }); 296 | if (child_it == children.end()) { 297 | // Not implemented, currently. Not sure if we need this? 298 | assert(false); 299 | throw nullptr; 300 | } else { 301 | // Recurse into the child 302 | 303 | // TODO: Support nested instances. 304 | // We will need to capture all different branches that 305 | // can reach the given subrange in the InstanceHandle 306 | // for this! 307 | assert(std::next(child_it) == children.end() || std::next(child_it)->instance_id == 0); 308 | 309 | return MakeInstanceHandle(*child_it, subrange); 310 | } 311 | } 312 | } 313 | 314 | 315 | clang::SourceManager& getSourceMgr() override { 316 | return sm; 317 | } 318 | 319 | clang::SourceManager& sm; 320 | 321 | SourceNode root; 322 | }; 323 | 324 | /** 325 | * Returns true if the function 326 | * i) uses "auto" or "decltype(auto)" for the return type 327 | * ii) does not use trailing return type specification 328 | */ 329 | static bool FunctionReturnTypeIsDeducedFromBody(clang::ASTContext& context, clang::FunctionDecl* decl) { 330 | clang::QualType returntype = decl->getReturnType(); 331 | auto auto_type = returntype->getContainedAutoType(); 332 | if (!auto_type) { 333 | auto_type = returntype.getDesugaredType(context)->getContainedAutoType(); 334 | } 335 | 336 | return (auto_type && !auto_type->hasAutoForTrailingReturnType()); 337 | } 338 | 339 | /** 340 | * Utility AST visitor that traverses a function and checks whether there is a need 341 | * to explicitly specialize it (e.g. because other transformations depend on types being well-known) 342 | */ 343 | class FunctionNeedsExplicitSpecializationChecker : public clang::RecursiveASTVisitor { 344 | public: 345 | FunctionNeedsExplicitSpecializationChecker(clang::FunctionDecl* decl) { 346 | // If this is a function template specialization, continue, otherwise we trivially don't need to specialize this function 347 | if (decl->getPrimaryTemplate() != nullptr && !decl->getTemplateSpecializationInfo()->isExplicitSpecialization()) { 348 | TraverseFunctionDecl(decl); 349 | needs_specialization = true; // TODO: Revert 350 | } 351 | } 352 | 353 | operator bool() const { 354 | return needs_specialization; 355 | } 356 | 357 | bool VisitIfStmt(clang::IfStmt* stmt) { 358 | if (features::constexpr_if && stmt->isConstexpr()) { 359 | needs_specialization = true; 360 | // Abort traversal for this check 361 | return false; 362 | } 363 | 364 | return true; 365 | } 366 | 367 | bool needs_specialization = false; 368 | }; 369 | 370 | /** 371 | * Utility AST visitor that determines the size of the parameter pack expanded 372 | * by the given PackExpansionExpr based on an implicitly specialized 373 | * FunctionDecl. 374 | * 375 | * This helper is provided because libclang provides no direct means of getting 376 | * the size of a parameter pack used for a specialization of a variadic 377 | * function template. 378 | * 379 | * Internally, this helper traverses the entire function (up to the point of 380 | * parameter pack expansion) to find the immediate children of expansion_expr 381 | * in the function body and to count the number of their appearances. 382 | * When using this helper, be cautious about the performance implications of 383 | * this full traversal. 384 | * 385 | * @note One might be tempted to assume we could just calculate the parameter 386 | * pack size as the difference between the total number of arguments used 387 | * for the current specialization and the number of non-variadic template 388 | * parameters. E.g. for "template void f()" 389 | * and the specialization "f", this would yield the 390 | * correct value 3 - 1 = 2. 391 | * However, that breaks e.g. for function templates like 392 | * "template void f(Us... u)". 393 | * Maybe this approach could work with less naive inference rules, but 394 | * I haven't further explored that idea. 395 | * 396 | * @note We also can't get this info from 397 | * FunctionDecl::getTemplateSpecializationInfo (which provides a 398 | * template argument list), since we don't know the expanded parameter 399 | * pack. We might get away with just taking any parameter pack contained 400 | * within the expanded expression, but there are contrived (and evil) 401 | * examples of referencing multiple parameter packs in the same 402 | * expansion. 403 | * That said, maybe this could be used as a faster default, and the full 404 | * function traversal could be used as a reliable fallback for contrived 405 | * examples. 406 | */ 407 | class DetermineParameterPackSizeVisitor : public clang::RecursiveASTVisitor { 408 | public: 409 | DetermineParameterPackSizeVisitor(clang::FunctionDecl* decl, clang::PackExpansionExpr* expansion_expr) : expr(expansion_expr->getPattern()) { 410 | TraverseFunctionDecl(decl); 411 | } 412 | 413 | operator size_t() const { 414 | return count; 415 | } 416 | 417 | bool VisitStmt(clang::Stmt* stmt) { 418 | // Compared for equality based on StmtClass and SourceLocations 419 | // Find the first statement that was generated from the parameter pack expansion. 420 | // We recognize this statement by comparing against the StmtClass and source location 421 | auto is_generated_stmt = [this](clang::Stmt* candidate) { 422 | auto expected_stmt_class = expr->getStmtClass(); 423 | 424 | if (clang::CXXUnresolvedConstructExpr::classof(expr) && clang::CXXFunctionalCastExpr::classof(candidate)) { 425 | // CXXUnresolvedConstructExprs get turned into 426 | // CXXFunctionalCastExprsGenerated in implicit specializations. 427 | // The SourceRange doesn't change, so we can still use it for 428 | // the purpose of comparison. 429 | // This was observed e.g. in "func(T{}...)". 430 | 431 | expected_stmt_class = candidate->getStmtClass(); 432 | } else if (clang::ImplicitCastExpr::classof(candidate)) { 433 | // Generated expressions are often wrapped in a generated 434 | // ImplicitCastExpr, so unfold that one by refering to the 435 | // child instead. 436 | // In particular, this occurs in expressions like "func((t)...)" 437 | 438 | // There should only be one child in this expression 439 | assert(std::distance(candidate->child_begin(), candidate->child_end()) == 1); 440 | 441 | candidate = *candidate->child_begin(); 442 | } 443 | 444 | return candidate->getStmtClass() == expected_stmt_class && 445 | candidate->getSourceRange() == expr->getSourceRange(); 446 | }; 447 | auto count = std::count_if(stmt->child_begin(), stmt->child_end(), is_generated_stmt); 448 | if (count) { 449 | this->count = count; 450 | // Abort traversal if we found the expression (TODO: Does this abort the entire traversal or just move back to parent? Abort the entire thing if the latter!) 451 | return false; 452 | } else { 453 | // Keep looking for a generated statement 454 | // NOTE: If the parameter pack was empty, this algorithm will need to scan the entire function to detect that :/ 455 | return true; 456 | } 457 | } 458 | 459 | clang::Expr* expr; // Pattern of the given PackExpansionExpr 460 | size_t count = 0; 461 | }; 462 | 463 | clang::ParmVarDecl* ASTVisitor::CurrentFunctionInfo::FindTemplatedParamDecl(clang::ParmVarDecl* specialized) const { 464 | auto it = std::find_if(parameters.begin(), parameters.end(), 465 | [specialized](const Parameter& param) { 466 | auto it = std::find_if(param.specialized.begin(), param.specialized.end(), 467 | [=](const Parameter::SpecializedParameter& parameter) { 468 | return (parameter.decl == specialized); 469 | }); 470 | return (it != param.specialized.end()); 471 | }); 472 | if (it == parameters.end()) { 473 | return nullptr; 474 | } 475 | 476 | return it->templated; 477 | } 478 | 479 | const std::vector& ASTVisitor::CurrentFunctionInfo::FindSpecializedParamDecls(clang::ParmVarDecl* templated) const { 480 | auto it = std::find_if(parameters.begin(), parameters.end(), 481 | [templated](const Parameter& param) { 482 | return (param.templated == templated); 483 | }); 484 | assert (it != parameters.end()); 485 | 486 | return it->specialized; 487 | } 488 | 489 | // TODO: Move elsewhere 490 | bool ASTVisitor::VisitSizeOfPackExpr(clang::SizeOfPackExpr* expr) { 491 | if (!current_function) { 492 | return true; 493 | } 494 | rewriter->ReplaceTextIncludingEndToken({ expr->getLocStart(), expr->getLocEnd() }, "/*" + GetClosedStringFor(expr->getLocStart(), expr->getLocEnd()) + "*/" + std::to_string(expr->getPackLength())); 495 | return true; 496 | } 497 | 498 | bool ASTVisitor::VisitPackExpansionExpr(clang::PackExpansionExpr* expr) { 499 | if (!current_function_template) 500 | return true; 501 | 502 | 503 | // NOTE: We only ever visit this once, in the general template. So we need to iterate over all implicit specializations of this function and fill in the gaps ourselves later. 504 | current_function_template->param_pack_expansions.push_back(FunctionTemplateInfo::ParamPackExpansionInfo{expr, std::vector{}}); 505 | std::cerr << "Visiting pack expansion, registering to " << current_function_template << std::endl; 506 | 507 | return true; 508 | } 509 | 510 | bool ASTVisitor::TraversePackExpansionExpr(clang::PackExpansionExpr* expr) { 511 | if (!current_function_template) 512 | return true; 513 | 514 | assert(!current_function_template->in_param_pack_expansion); 515 | current_function_template->in_param_pack_expansion = true; 516 | Parent::TraversePackExpansionExpr(expr); 517 | current_function_template->in_param_pack_expansion = false; 518 | return true; 519 | } 520 | 521 | bool ASTVisitor::VisitDeclRefExpr(clang::DeclRefExpr* expr) { 522 | // Record uses of parameter packs within pack expansions 523 | 524 | if (!current_function_template || !current_function_template->in_param_pack_expansion) { 525 | return true; 526 | } 527 | 528 | auto parm_var_decl = clang::dyn_cast(expr->getDecl()); 529 | if (parm_var_decl && parm_var_decl->isParameterPack()) { 530 | current_function_template->param_pack_expansions.back().referenced_packs.push_back(expr); 531 | } 532 | return true; 533 | } 534 | 535 | static std::string MakeUniqueParameterPackName(clang::ParmVarDecl* decl, size_t index) { 536 | // Just append a 1-based counter for now 537 | // TODO: This will break if there is already a parameter with the new name 538 | return decl->getNameAsString() + std::to_string(1 + index); 539 | } 540 | 541 | // Replace a function's return type with the given string. 542 | // Note that if a function template specialization should be 543 | // rewritten, "decl" should be passed the templated FunctionDecl 544 | // instead since it's used to gather the SourceLocations. 545 | static void ReplaceReturnType(RewriterBase& rewriter, clang::FunctionDecl& decl, llvm::StringRef new_type) { 546 | // NOTE: For "decltype(auto)", decl.getReturnTypeSourceRange() actually 547 | // stops at "(auto)", so we instead use its start location and then 548 | // replace everything up to the function name 549 | rewriter.ReplaceTextExcludingEndToken({decl.getReturnTypeSourceRange().getBegin(), decl.getNameInfo().getLoc()}, new_type.str() + " "); 550 | } 551 | 552 | bool ASTVisitor::TraverseFunctionTemplateDecl(clang::FunctionTemplateDecl* decl) { 553 | if (context.getFullLoc(decl->getLocStart()).isInSystemHeader()) { 554 | // Skip system header contents 555 | return true; 556 | } 557 | 558 | WalkUpFromFunctionTemplateDecl(decl); 559 | 560 | std::cerr << "Visiting FunctionTemplateDecl:" << decl << std::endl; 561 | auto templated_decl = decl->getTemplatedDecl(); 562 | { 563 | // This is the actual template definition (i.e. not one of the 564 | // specializations generated implicitly by clang). We do a prepass over 565 | // the template definition to gather a list of things that would be 566 | // difficult to rewrite otherwise, such as parameter pack expansions. 567 | 568 | auto [it, ignored] = function_templates.emplace(templated_decl, FunctionTemplateInfo{}); 569 | current_function_template = &it->second; 570 | std::cerr << "Template: " << templated_decl->getNameAsString() << std::endl; 571 | 572 | // TODO: Will we traverse this decl twice now? 573 | Parent::TraverseFunctionDecl(templated_decl); 574 | 575 | current_function_template = nullptr; 576 | 577 | } 578 | 579 | // The rest of this function is concerned with generating explicit 580 | // specializations from what's an implicit template specialization in 581 | // libclang's AST. Hence, return early from this code path. 582 | 583 | // TODO: There may be multiple overloads with the same function name but 584 | // different sets of deduced return values. To make sure we support 585 | // all of these, we need to append a *mangled* version of the 586 | // function name here! 587 | const std::string auto_deduction_helper_struct_name = "cftf_deduced_return_type_" + decl->getNameAsString(); 588 | const bool deduce_return_type = FunctionReturnTypeIsDeducedFromBody(context, templated_decl); 589 | 590 | for (auto* specialized_decl : decl->specializations()) { 591 | std::cerr << "Specialization " << specialized_decl << std::endl; 592 | 593 | bool specialize = FunctionNeedsExplicitSpecializationChecker(specialized_decl); 594 | 595 | decltype(rewriter) old_rewriter; 596 | 597 | CurrentFunctionInfo current_function = { specialized_decl, {} }; 598 | std::string template_argument_string; // TODO: This is initialized below (two indentation levels deeper), which is rather ugly... 599 | if (specialize) { 600 | current_function.template_info = &function_templates.at(templated_decl); 601 | 602 | // Temporarily exchange our clang::Rewriter with an internal rewriter that writes to a copy of the current function (which will act as an explicit instantiation) 603 | // TODO: This will fail sooner or later; functions can be nested e.g. by declaring a class inside a function! 604 | // TODO: Should probably use the locations from templated_decl instead! 605 | old_rewriter = std::exchange(rewriter, std::make_unique(rewriter->getSourceMgr(), clang::SourceRange{ specialized_decl->getLocStart(), getLocForEndOfToken(specialized_decl->getLocEnd()) })); 606 | 607 | // Add template argument list for this specialization 608 | { 609 | llvm::raw_string_ostream ss(template_argument_string); 610 | assert(specialized_decl->getTemplateSpecializationArgs()); 611 | auto&& template_args = specialized_decl->getTemplateSpecializationArgs()->asArray(); 612 | for (auto it = template_args.begin(); it != template_args.end(); ++it) { 613 | if (it != template_args.begin()) { 614 | ss << ", "; 615 | } 616 | clang::LangOptions policy; // TODO: Get this from the proper source! 617 | if (it->getKind() == clang::TemplateArgument::Pack) { 618 | // Print each item in the parameter pack individually 619 | for (auto pack_it = it->pack_begin(); pack_it < it->pack_end(); ++pack_it) { 620 | if (pack_it != it->pack_begin()) { 621 | ss << ", "; 622 | } 623 | pack_it->print(policy, ss); 624 | } 625 | } else { 626 | it->print(policy, ss); 627 | } 628 | } 629 | ss.flush(); 630 | // TODO: Templated_decl locs! 631 | rewriter->InsertTextAfter(specialized_decl->getLocation(), '<' + template_argument_string + '>'); 632 | } 633 | 634 | // The template generally contains references to the template parameters (in the body and in the function parameter list). 635 | // This is a problem in our generated specializations, which don't define the template parameters (i.e. there is no 636 | // "template" preceding them) but must use the actual template arguments instead. 637 | // We address this as follows: 638 | // * In the specialization body, we insert type aliases and constants at the top to manually declare template parameters. 639 | // This is much easier than trying to manually replace all occurrences of template parameters with concrete arguments. 640 | // * The parameter list is replaced by the FunctionDecl parameter list provided by clang. Stringifying this correctly 641 | // is reasonably easy and gets rid of all template parameter references automatically. 642 | // 643 | // NOTE: We only need to replace anything for non-empty parameter lists, but note that a specialization's parameter list 644 | // may well be empty while the actual template function's parameter list is not. In particular, this happens for 645 | // template functions of the form 646 | // 647 | // template void func(T... t) 648 | // 649 | // when specialized for empty parameter packs. 650 | 651 | std::transform(templated_decl->param_begin(), templated_decl->param_end(), std::back_inserter(current_function.parameters), 652 | [&](clang::ParmVarDecl* templated_param_decl) { 653 | // TODO: Unify this with FindTemplatedDecl! 654 | auto is_same_decl = [&](const clang::ParmVarDecl* specialized_param_decl) { 655 | // Unfortunately, there doesn't seem to be a better way to do this than to compare the parameters by name... 656 | return (specialized_param_decl->getName() == templated_param_decl->getName()); 657 | }; 658 | auto first_it = std::find_if (specialized_decl->param_begin(), specialized_decl->param_end(), is_same_decl); 659 | auto last_it = std::find_if_not(first_it, specialized_decl->param_end(), is_same_decl); 660 | 661 | CurrentFunctionInfo::Parameter ret { templated_param_decl, {} }; 662 | 663 | if (first_it + 1 == last_it) { 664 | // Just one argument 665 | ret.specialized.push_back({*first_it, templated_param_decl->getNameAsString()}); 666 | } else { 667 | // Templated parameter refers to a parameter pack for which multiple (or none) arguments were generated; 668 | // to prevent name collisions, generate a unique name for each of them. 669 | for (auto it = first_it; it != last_it; ++it) { 670 | std::string unique_name = MakeUniqueParameterPackName(templated_param_decl, std::distance(first_it, it)); 671 | ret.specialized.push_back({*it, std::move(unique_name)}); 672 | } 673 | } 674 | 675 | return ret; 676 | }); 677 | 678 | 679 | // Remove empty parameter packs from the specialized signature 680 | // (Non-empty parameter packs are handled in VisitVarDecl) 681 | for (auto templated_parameter_it = templated_decl->param_begin(); 682 | templated_parameter_it != templated_decl->param_end(); 683 | ++templated_parameter_it) { 684 | auto* templated_parameter = *templated_parameter_it; 685 | if (templated_parameter->isParameterPack() && current_function.FindSpecializedParamDecls(templated_parameter).empty()) { 686 | const bool is_first_parameter = (templated_parameter_it == templated_decl->param_end()); 687 | const bool is_last_parameter = (std::next(templated_parameter_it) == templated_decl->param_end()); 688 | 689 | // Remove the parameter (including any preceding or following commas) 690 | clang::SourceLocation start_loc = is_first_parameter ? templated_decl->parameters().front()->getLocStart() : templated_parameter->getLocStart(); 691 | if (is_last_parameter) { 692 | // Delete up to the end of the function signature 693 | clang::SourceLocation end_loc = templated_decl->parameters().back()->getLocEnd(); 694 | rewriter->ReplaceTextIncludingEndToken({start_loc, end_loc}, ""); 695 | } else { 696 | // Delete up to the beginning of the next parameter 697 | auto end_loc = (*std::next(templated_parameter_it))->getLocStart(); 698 | rewriter->ReplaceTextExcludingEndToken({start_loc, end_loc}, ""); 699 | } 700 | } 701 | } 702 | } 703 | 704 | // From here on below, assume we have a self-contained definition that we can freely rewrite code in 705 | this->current_function = current_function; 706 | 707 | // Patch up body (parameter pack expansions, fold expressions) 708 | /*if (decl2->getPrimaryTemplate())*/ { 709 | auto current_function_template_it = function_templates.find(templated_decl); 710 | if (current_function_template_it != function_templates.end()) { 711 | auto current_function_template = ¤t_function_template_it->second; 712 | assert(current_function_template); 713 | 714 | for (auto [pack_expansion_expr, pack_uses] : current_function_template->param_pack_expansions) { 715 | auto rewriter = static_cast(this->rewriter.get()); 716 | auto* pattern = pack_expansion_expr->getPattern(); 717 | auto range_end = clang::Lexer::getLocForEndOfToken(pack_expansion_expr->getEllipsisLoc(), 0, rewriter->getSourceMgr(), {}); 718 | auto base_instance = rewriter->MakeInstanceHandle({pattern->getLocStart(), range_end}); 719 | 720 | size_t pack_length = DetermineParameterPackSizeVisitor { specialized_decl, pack_expansion_expr }; 721 | 722 | std::vector instances; 723 | for (size_t instance_id = 0; instance_id < pack_length; ++instance_id) { 724 | instances.push_back(rewriter->CreateNewInstance(base_instance)); 725 | } 726 | 727 | // Delete the original pack expansion first, then re-add one copy for each parameter pack element 728 | rewriter->ReplaceTextExcludingEndToken(base_instance, {pattern->getLocStart(), range_end}, "/*" + GetClosedStringFor(pattern->getLocStart(), range_end) + " of size " + std::to_string(pack_length) + "*/"); 729 | 730 | for (size_t instance_id = 0; instance_id < pack_length; ++instance_id) { 731 | // Insert separators for all but the last instance 732 | const char* replacement = ", "; 733 | if (instance_id == pack_length - 1) { 734 | // No separator needed, so just remove the ellipsis 735 | replacement = ""; 736 | } 737 | rewriter->ReplaceTextExcludingEndToken(instances[instance_id], {pack_expansion_expr->getEllipsisLoc(), range_end}, replacement); 738 | 739 | // We generate unique names for function parameters 740 | // expanded from parameter packs. Those now need to be 741 | // patched into the function body whenever the parameter 742 | // pack is referenced. 743 | for (auto* pack_expr : pack_uses) { 744 | clang::SourceRange range = { pack_expr->getLocStart(), clang::Lexer::getLocForEndOfToken(pack_expr->getLocEnd(), 0, rewriter->getSourceMgr(), {}) }; 745 | auto parm_var_decl = clang::dyn_cast(pack_expr->getDecl()); 746 | assert(parm_var_decl && parm_var_decl->isParameterPack()); 747 | 748 | const auto& unique_name = current_function.FindSpecializedParamDecls(parm_var_decl)[instance_id].unique_name; 749 | rewriter->ReplaceTextExcludingEndToken(instances[instance_id], range, unique_name); 750 | } 751 | 752 | // TODO: Also patch "T..." uses in the function body 753 | } 754 | } 755 | } 756 | } 757 | 758 | Parent::TraverseFunctionDecl(specialized_decl); 759 | 760 | this->current_function = std::nullopt; 761 | 762 | if (specialize) { 763 | // Fix up references to template parameters in the specialization by adding an explicit 764 | // declaration of them at the top of the specialization body 765 | auto template_parameters = decl->getTemplateParameters(); 766 | auto specialization_args = specialized_decl->getTemplateSpecializationArgs()->asArray(); 767 | assert(template_parameters); 768 | assert(template_parameters->size() == specialization_args.size()); 769 | std::string aliases = "\n"; 770 | auto parameter_it = template_parameters->begin(); 771 | auto argument_it = specialization_args.begin(); 772 | for (; parameter_it != template_parameters->end(); ++parameter_it, ++argument_it) { 773 | auto& parameter = *parameter_it; 774 | auto& argument = *argument_it; 775 | assert(parameter); 776 | 777 | if (parameter->getNameAsString().empty()) { 778 | // If the parameter was never named, we don't need to reexport it 779 | continue; 780 | } 781 | 782 | switch (argument.getKind()) { 783 | case clang::TemplateArgument::Type: 784 | // e.g. template 785 | aliases += "using " + parameter->getNameAsString() + " = " + argument.getAsType().getAsString() + ";\n"; 786 | break; 787 | 788 | case clang::TemplateArgument::Integral: 789 | // e.g. template 790 | // TODO: Get the actual (possibly const-qualified) type! 791 | aliases += "auto " + parameter->getNameAsString() + " = " + argument.getAsIntegral().toString(10) + ";\n"; 792 | break; 793 | 794 | case clang::TemplateArgument::Declaration: 795 | // e.g. template with Ptr=&some_global_variable 796 | std::cerr << "WARNING: TemplateArgument::Declaration not unsupported, yet" << std::endl; 797 | aliases += "TODO " + parameter->getNameAsString() + " = TODO;\n"; 798 | break; 799 | 800 | case clang::TemplateArgument::NullPtr: 801 | // e.g. template with Ptr=nullptr 802 | aliases += "decltype(nullptr) " + parameter->getNameAsString() + " = nullptr;\n"; 803 | break; 804 | 805 | case clang::TemplateArgument::Template: 806 | // e.g. template Templ> 807 | // TODO: How should we handle these? Function bodies can't include templates! 808 | // TODO: Instead of ignoring this error, abort specializing this template 809 | std::cerr << "WARNING: Template template parameters unsupported" << std::endl; 810 | aliases += "TODO template " + parameter->getNameAsString() + " = TODO;\n"; 811 | break; 812 | 813 | case clang::TemplateArgument::Pack: 814 | // e.g. template 815 | // e.g. template 816 | // e.g. template... Templs> 817 | 818 | // We don't need to do anything here, since we expand all parameter packs in the function body 819 | std::cerr << "WARNING: Variadic templates support is incomplete" << std::endl; 820 | break; 821 | 822 | default: 823 | std::cerr << "WARNING: Unsupported template argument type: " << static_cast(argument.getKind()) << std::endl; 824 | aliases += "TODO " + parameter->getNameAsString() + " = TODO;\n"; 825 | assert(false); 826 | break; 827 | } 828 | } 829 | 830 | // TODO: Templated_decl locations 831 | rewriter->InsertTextAfter(specialized_decl->getBody()->getLocStart(), aliases); 832 | 833 | ReplaceReturnType(*rewriter, *templated_decl, specialized_decl->getReturnType().getAsString()); 834 | 835 | // Return type deduction: Specialize helper type trait for the 836 | // template arguments used in this function specialization 837 | std::string deduced_return_type; 838 | if (deduce_return_type) { 839 | deduced_return_type = "template<>\nstruct " + auto_deduction_helper_struct_name + "<"; 840 | deduced_return_type += template_argument_string; 841 | deduced_return_type += "> {\n using type = "; 842 | deduced_return_type += specialized_decl->getReturnType().getAsString(); 843 | deduced_return_type += ";\n};\n"; 844 | } 845 | 846 | // Finalize the generated specialization 847 | std::swap(rewriter, old_rewriter); 848 | std::string content = static_cast(old_rewriter.get())->GetContents(); 849 | rewriter->InsertTextAfter(specialized_decl->getLocEnd(), "\n\n// Specialization generated by CFTF\n" + deduced_return_type + "\ntemplate<>\n" + content); 850 | } 851 | } 852 | 853 | // TODO: Only if we actually explicitly specialized anything! 854 | // Now that all explicit specializations have been generated, remove 855 | // the original template function definition since it still contains 856 | // unmodified "future" C++ code 857 | rewriter->ReplaceTextIncludingEndToken(templated_decl->getBody()->getSourceRange(), ";"); 858 | 859 | // Replace "auto"/"decltype(auto)" return type with a deduced type 860 | // (introduced in C++14 via N3638). Since the deduced type may depend on 861 | // template parameters, this is done using a helper type trait that maps 862 | // template arguments to the deduced return type 863 | if (deduce_return_type) { 864 | auto* template_parameters = decl->getTemplateParameters(); 865 | 866 | // First, declare the helper type trait (which will have a separate 867 | // definition generated for each implicit specialization) 868 | std::string deduced_return_type_decl = "template"; 869 | deduced_return_type_decl += GetClosedStringFor(template_parameters->getLAngleLoc(), template_parameters->getRAngleLoc()); 870 | deduced_return_type_decl += "\nstruct " + auto_deduction_helper_struct_name + ";\n\n"; 871 | // NOTE: templated_decl->getLocStart() starts *after* the 872 | // template part, so we indeed need decl->getLocStart() 873 | // here instead 874 | rewriter->ReplaceTextExcludingEndToken({decl->getLocStart(), decl->getLocStart()}, deduced_return_type_decl); 875 | 876 | // Second, replace "auto" by referring to the helper type trait 877 | std::string deduced_return_type_string = "typename " + auto_deduction_helper_struct_name + "<"; 878 | bool first_parameter = true; 879 | for (auto& parameter : template_parameters->asArray()) { 880 | if (!first_parameter) { 881 | deduced_return_type_string += ", "; 882 | } 883 | first_parameter = false; 884 | deduced_return_type_string += parameter->getNameAsString(); 885 | } 886 | deduced_return_type_string += ">::type"; 887 | ReplaceReturnType(*rewriter, *templated_decl, deduced_return_type_string); 888 | } 889 | 890 | return true; 891 | } 892 | 893 | bool ASTVisitor::VisitFunctionDecl(clang::FunctionDecl* decl) { 894 | if (decl->getDescribedFunctionTemplate() || decl->isFunctionTemplateSpecialization()) { 895 | // If this function is templated (either a generic definition or a 896 | // specialization), skip it since we handled it in 897 | // TraverseFunctionTemplateDecl already 898 | return true; 899 | } 900 | 901 | if (FunctionReturnTypeIsDeducedFromBody(context, decl)) { 902 | ReplaceReturnType(*rewriter, *decl, decl->getReturnType().getAsString()); 903 | } 904 | 905 | return true; 906 | } 907 | 908 | static std::string RebuildVarDecl(clang::SourceManager& sm, clang::VarDecl* decl) { 909 | // TODO: Turn types like pseudo-code "(int[5])&& array" (currently printed as "int &&[5] t") into "int (&&t)[5]" 910 | std::string new_decl = clang::QualType::getAsString(decl->getType().getSplitDesugaredType(), clang::PrintingPolicy{{}}); 911 | new_decl += ' ' + decl->getName().str(); 912 | if (auto init = decl->getInit()) { 913 | new_decl += " = " + SourceRangeToString(sm, { init->getLocStart(), clang::Lexer::getLocForEndOfToken(init->getLocEnd(), 0, sm, {})}); 914 | } 915 | return new_decl; 916 | } 917 | 918 | namespace ranges { 919 | 920 | template 921 | struct reverse_iterator { 922 | reverse_iterator& operator++() { 923 | --it; 924 | return *this; 925 | } 926 | 927 | auto operator* () { 928 | return *std::prev(it); 929 | } 930 | 931 | auto operator* () const { 932 | return *std::prev(it); 933 | } 934 | 935 | bool operator!=(reverse_iterator oth) { 936 | return it != oth.it; 937 | } 938 | 939 | It it; 940 | }; 941 | 942 | template 943 | struct reversed { 944 | using ForwardIt = decltype(std::declval().begin()); 945 | using ForwardEndIt = decltype(std::declval().end()); 946 | 947 | using iterator = reverse_iterator; 948 | 949 | reversed(Rng&& rng) : rng(std::forward(rng)) {} 950 | 951 | iterator begin() const { 952 | return { rng.end() }; 953 | } 954 | 955 | iterator end() const { 956 | return { rng.begin() }; 957 | } 958 | 959 | Rng&& rng; 960 | }; 961 | 962 | } // namespace ranges 963 | 964 | bool ASTVisitor::VisitDeclStmt(clang::DeclStmt* stmt) { 965 | if (!IsInFullySpecializedFunction()) { 966 | return true; 967 | } 968 | 969 | // The types used in declarations might be dependent on template 970 | // parameters. That's not an issue usually since we re-export template 971 | // parameter names in the specialized template, however for parameter packs 972 | // this cannot be done. In those cases, we just replace the declaration 973 | // type by the desugared type to get rid of the template parameter uses. 974 | // 975 | // Clang doesn't provide us with the SourceLocations to the type, so 976 | // we need to replace the entire declaration with a manually crafted one 977 | // instead of replacing just the type. 978 | // 979 | // When doing these rewrites, we need to be careful about multiple 980 | // variables declared in the same line, 981 | // e.g. "stuff first, *second = &first;"). 982 | // The easiest way to make sure we do this correctly is to just split up 983 | // the declarations into separate statements. 984 | 985 | for (auto decl : ranges::reversed(stmt->decls())) { 986 | if (auto var_decl = clang::dyn_cast(decl)) { 987 | // TODO: This needs to be more sophisticated for inplace-defined struct types! 988 | auto new_decl = RebuildVarDecl(rewriter->getSourceMgr(), var_decl) + ';'; 989 | rewriter->InsertTextAfter(clang::Lexer::getLocForEndOfToken(stmt->getLocEnd(), 0, rewriter->getSourceMgr(), {}), new_decl); 990 | } else if (clang::StaticAssertDecl::classof(decl)) { 991 | // Nothing to do 992 | } else { 993 | std::cerr << "WARNING: Unimplemented Decl: " << decl->getDeclKindName() << std::endl; 994 | } 995 | } 996 | 997 | // Delete the old declaration(s) 998 | rewriter->ReplaceTextIncludingEndToken(stmt->getSourceRange(), ""); 999 | 1000 | return true; 1001 | } 1002 | 1003 | clang::Decl* ASTVisitor::FunctionTemplateInfo::FindTemplatedDecl(clang::SourceManager& sm, clang::Decl* specialized) const { 1004 | auto is_templated_decl = [&sm,specialized](clang::Decl* candidate) { 1005 | // Heuristic to match Decls against each other: 1006 | // * DeclKind must be the same 1007 | // * The SourceRange of the specialized Decl must be fully covered by 1008 | // the templated one (they don't need to be equal because specialized 1009 | // Decls may e.g. exclude the "..." from parameter packs, etc) 1010 | return candidate->getKind() == specialized->getKind() && 1011 | sm.isPointWithin(specialized->getLocStart(), candidate->getLocStart(), candidate->getLocEnd()) && 1012 | sm.isPointWithin(specialized->getLocEnd(), candidate->getLocStart(), candidate->getLocEnd()); 1013 | }; 1014 | 1015 | auto match_it = std::find_if(decls.begin(), decls.end(), is_templated_decl); 1016 | if (match_it == decls.end()) { 1017 | return nullptr; 1018 | } 1019 | return *match_it; 1020 | } 1021 | 1022 | bool ASTVisitor::VisitVarDecl(clang::VarDecl* decl) { 1023 | if (!IsInFullySpecializedFunction()) { 1024 | if (current_function_template) { 1025 | current_function_template->decls.push_back(decl); 1026 | } 1027 | return true; 1028 | } 1029 | 1030 | // TODO: If the type of the declared variable is dependent on a template parameter, replace it 1031 | // NOTE: We only need to replace dependent types, but since it would be extra work to determine whether a type is dependent, we just apply this transformation to all types for now 1032 | // TODO: To reduce the danger of incorrect transformations, we should probably put in the extra work :/ 1033 | 1034 | { 1035 | // NOTE: getParents() only seems to return reliable results when called 1036 | // on the templated declaration rather than on decl directly. 1037 | auto templated_decl = current_function->template_info->FindTemplatedDecl(rewriter->getSourceMgr(), decl); 1038 | assert(templated_decl); 1039 | auto parents = context.getParents(*templated_decl); 1040 | auto node_is_decl_stmt = [](auto& node) { 1041 | auto ptr = node.template get(); 1042 | return ptr && clang::DeclStmt::classof(ptr); 1043 | }; 1044 | if (std::any_of(parents.begin(), parents.end(), node_is_decl_stmt)) { 1045 | std::cerr << "Skipping VarDecl visitation because it was already handled in VisitDeclStmt" << std::endl; 1046 | return true; 1047 | } 1048 | } 1049 | 1050 | 1051 | // Ideally, we'd just rewrite the type in this declaration. However, libclang provides no way to get the SourceRange for this, so we instead rebuild a new declaration from scratch... 1052 | // NOTE: We silently drop the default arguments from the specialized 1053 | // signature. Keeping them certainly wouldn't be legal C++, since 1054 | // they are just the same as specified in the templated function 1055 | // declaration. 1056 | auto new_decl = RebuildVarDecl(rewriter->getSourceMgr(), decl); 1057 | if (auto pv_decl = clang::dyn_cast(decl)) { 1058 | // This is part of a function signature, so the declaration needs more 1059 | // complicated treatment than other declarations since it could have 1060 | // been generated from an expanded parameter pack 1061 | 1062 | auto templated = current_function->FindTemplatedParamDecl(pv_decl); 1063 | assert(templated); 1064 | 1065 | if (templated->isParameterPack()) { 1066 | std::string addendum; 1067 | bool first_parameter = true; 1068 | for (auto& parameter_and_unique_name : current_function->FindSpecializedParamDecls(templated)) { 1069 | auto* parameter = parameter_and_unique_name.decl; 1070 | 1071 | if (!first_parameter) { 1072 | addendum += ", "; 1073 | } 1074 | first_parameter = false; 1075 | 1076 | // TODO: For on-the-fly declared template arguments like e.g. in "func()", getAsString will print spam such as "struct(anonymous namespace)::unnamed". We neither want that namespace nor do we want the "struct" prefix! 1077 | // NOTE: CppInsights has a lot more code to handle getting the parameter name and type... 1078 | addendum += parameter->getType().getAsString(); 1079 | if (!parameter->getNameAsString().empty()) { 1080 | addendum += " "; 1081 | // Parameter generated from a parameter pack will be assigned the same name, 1082 | // so we need to distinguish the generated parameter names manually. 1083 | addendum += parameter_and_unique_name.unique_name; 1084 | } 1085 | } 1086 | 1087 | // NOTE: decl->getLocEnd() and templated->getLocEnd() return 1088 | // different results in some cases. E.g. for 1089 | // "decltype(T{}) t", the former coincides with the start of 1090 | // the declaration, whereas the latter correctly spans the 1091 | // entire declaration. Similarly, for unnamed parameter packs 1092 | // such as "T...", decl->getLocEnd() stops before the 1093 | // ellipsis, whereas templated->getLocEnd() includes it 1094 | rewriter->ReplaceTextIncludingEndToken(templated->getSourceRange(), "/*Expansion of " + GetClosedStringFor(decl->getLocStart(), templated->getLocEnd()) + "{-*/" + addendum + "/*-}*/"); 1095 | } else { 1096 | auto old_decl = GetClosedStringFor(decl->getLocStart(), decl->getLocEnd()); 1097 | if (new_decl != old_decl) { 1098 | rewriter->ReplaceTextIncludingEndToken(decl->getSourceRange(), "/*" + old_decl + "*/" + new_decl); 1099 | } else { 1100 | std::cerr << "Skipped rewrite due to matching declarations" << std::endl; 1101 | } 1102 | } 1103 | } else { 1104 | // NOTE: In particular, VarDecls should always have been handled as 1105 | // part of VisitDeclStmt 1106 | std::cerr << "Unknown VarDecl kind " << decl->getDeclKindName() << std::endl; 1107 | assert(false); 1108 | } 1109 | 1110 | return true; 1111 | } 1112 | 1113 | 1114 | } // namespace cftf 1115 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This is just a dummy CMake project, used solely for creating compile_commands.json 2 | # Run this CMake script within the tests/ directory, i.e. do not attempt an out-of-source build 3 | cmake_minimum_required(VERSION 3.10) 4 | 5 | set(CMAKE_EXPORT_COMPILE_COMMANDS 1) 6 | 7 | project(tests) 8 | add_executable(tests 9 | n3638_return_type_deduction.cpp 10 | n3928_static_assert_with_optional_message.cpp 11 | test_main.cpp) 12 | set_property(TARGET tests PROPERTY CXX_STANDARD 17) 13 | set_property(TARGET tests PROPERTY CXX_STANDARD_REQUIRED ON) 14 | -------------------------------------------------------------------------------- /test/n3638_return_type_deduction.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | constexpr auto test1() { 4 | return 5; 5 | } 6 | 7 | constexpr decltype(auto) test1(int& a) { 8 | return a; 9 | } 10 | 11 | template 12 | constexpr auto test2() { 13 | if constexpr (sizeof(T) == sizeof(int)) { 14 | return char{}; 15 | } else { 16 | return int{}; 17 | } 18 | } 19 | 20 | #if CFTF_SUPPORT_ALIASING_FUNCTION_NAMES 21 | // Same function name as the previous test2 but with a dummy parameter 22 | template 23 | constexpr auto test2(int a) { 24 | if constexpr (sizeof(T) == sizeof(char)) { 25 | return char{}; 26 | } else { 27 | return T{}; 28 | } 29 | } 30 | #endif 31 | 32 | template 33 | constexpr auto test3a(T& val) { 34 | return val; 35 | } 36 | 37 | template 38 | constexpr decltype(auto) test3b(T& val) { 39 | return val; 40 | } 41 | 42 | template 43 | constexpr decltype(auto) test3c(T val) { 44 | return val; 45 | } 46 | 47 | template 48 | constexpr decltype(auto) test3d(T val) { 49 | #pragma clang diagnostic push 50 | #pragma clang diagnostic ignored "-Wreturn-stack-address" 51 | return (val); 52 | #pragma clang diagnostic pop 53 | } 54 | 55 | // Trailing return type specifications shouldn't be matched by return type deduction 56 | constexpr auto test4() -> char { 57 | return 5; 58 | } 59 | 60 | static_assert(std::is_same::value); 61 | int dummy; 62 | static_assert(std::is_same::value); 63 | static_assert(std::is_same()), int>::value); 64 | static_assert(std::is_same()), char>::value); 65 | #if CFTF_SUPPORT_ALIASING_FUNCTION_NAMES 66 | static_assert(std::is_same(dummy)), char>::value); 67 | static_assert(std::is_same(dummy)), int>::value); 68 | #endif 69 | static_assert(std::is_same::value); 70 | static_assert(std::is_same::value); 71 | static_assert(std::is_same::value); 72 | static_assert(std::is_same::value); 73 | static_assert(std::is_same::value); 74 | -------------------------------------------------------------------------------- /test/n3928_static_assert_with_optional_message.cpp: -------------------------------------------------------------------------------- 1 | // Static assert without assertion message (since C++17) 2 | static_assert(true); 3 | 4 | // Static assert with assertion message (since C++11) 5 | static_assert(true, "assertion message"); 6 | -------------------------------------------------------------------------------- /test/test_main.cpp: -------------------------------------------------------------------------------- 1 | int main() { 2 | // Do nothing. Currently, all tests are implemented via static_asserts 3 | } 4 | --------------------------------------------------------------------------------