使用 Clang 库,如何检查类是否与概念有效匹配

问题描述 投票:0回答:1

我有一个小代码,可以将 C++ 声明解析为类、模板类、函数等。概念,然后我想写下类的名称和概念的名称,这段代码表示该类对于概念的要求是否有效,如果不是,则说明哪些要求不满意。

这是我尝试实现的函数,但它不起作用,它总是返回 false,即使该类满足概念要求。所以我想找到一些如何使用 Clang Sema 分析概念的示例:

void CheckConceptUsage(Sema &SemaRef, const ConceptDecl *Concept, const CXXRecordDecl *Class) {
    // Get the type of the class.
    QualType ClassType = SemaRef.Context.getRecordType(Class);

    // Create a TemplateArgument representing the class type.
    TemplateArgument ClassTemplateArg(ClassType);

    // Prepare the necessary data structures for the constraint check.
    ConstraintSatisfaction Satisfaction;

    // Create a MultiLevelTemplateArgumentList
    MultiLevelTemplateArgumentList TemplateArgs;
     ArrayRef<TemplateArgument> TemplateArgsRef(ClassTemplateArg);
    TemplateArgs.addOuterTemplateArguments(const_cast<CXXRecordDecl *>(Class), TemplateArgsRef, /*Final*/ true);
    // TemplateArgs.addOuterTemplateArguments(ArrayRef<TemplateArgument>(ClassTemplateArg));


    // Retrieve the constraint expression associated with the concept
    const Expr *ConstraintExpr = Concept->getConstraintExpr();
    
    if (!ConstraintExpr) {
        llvm::outs() << "The concept " << Concept->getNameAsString() 
                     << " has no constraints (requires clause) to check.\n";
        return;
    }

    // Cast the constraint expression to RequiresExpr to access its components
    if (const RequiresExpr *ReqExpr = llvm::dyn_cast<RequiresExpr>(ConstraintExpr)) {
        std::cout << "--- CheckConceptUsage if " << std::endl;
        // Get the list of requirements (constraints) in the requires expression
        llvm::SmallVector<const Expr*, 4> ConstraintExprs;

        for (const auto &Requirement : ReqExpr->getRequirements()) {
            std::cout << "--- CheckConceptUsage for " << std::endl;
            if (const auto *ExprReq = llvm::dyn_cast<clang::concepts::ExprRequirement>(Requirement)) {
                // Handle expression requirements
                std::cout << "--- CheckConceptUsage ExprRequirement" << std::endl;
                ConstraintExprs.push_back(ExprReq->getExpr());
            } else if (const auto *TypeReq = llvm::dyn_cast<clang::concepts::TypeRequirement>(Requirement)) {
                // Handle type requirements by evaluating the type's instantiation dependency
                std::cout << "--- CheckConceptUsage TypeRequirement" << std::endl;
                QualType Type = TypeReq->getType()->getType();
                QualType DependentType = TypeReq->getType()->getType();
                if (Type->isDependentType()) {
                    std::cout << "--- CheckConceptUsage isDependentType" << std::endl;
                    // Create a pseudo-expression that checks if this type exists
                    // TypeTraitExpr *TraitExpr = TypeTraitExpr::Create(
                    //     SemaRef.Context, 
                    //     DependentType,
                    //     SourceLocation(), 
                    //     UTT_IsCompleteType,  // Use a type trait like "is complete type"
                    //     ArrayRef<QualType>(DependentType),
                    //     SourceLocation(), 
                    //     SemaRef.Context.BoolTy
                    // );
                    
                    // ConstraintExprs.push_back(TraitExpr);
                }
            }
        }

        
        std::cout << "--- CheckConceptUsage ConstraintExprs size:" << ConstraintExprs.size() << std::endl;

        // Now use the updated list of constraints in the satisfaction check
        bool IsSatisfied = SemaRef.CheckConstraintSatisfaction(
            Concept, 
            ConstraintExprs, 
            TemplateArgs, 
            Class->getSourceRange(), 
            Satisfaction
        );

        if (IsSatisfied) {
            llvm::outs() << "The class " << Class->getName() << " satisfies the concept " << Concept->getName() << ".\n";
        } else {
            llvm::outs() << "The class " << Class->getName() << " does NOT satisfy the concept " << Concept->getName() << ".\n";
        }
    } else {
        llvm::outs() << "The concept " << Concept->getNameAsString() 
                     << " does not have a valid requires expression.\n";
    }
}

这是我最小的可重现示例。

https://gist.github.com/alexst07/7dadf36ea663171e91778a77d01fbbda

c++ clang c++20 c++-concepts
1个回答
0
投票

原代码在 要点 离工作很近了。 我能够让它在给定的情况下工作 例如,另一个简单的例子,有一些变化。

问题:
CheckConstraintSatisfaction
返回值

Sema::CheckConstraintSatisfaction
回报:

如果发生错误且无法检查满意度,则为 true,否则为 false。

即表示是否可以进行满意度检查,而不是 是否满足约束条件。

因此,该行:

        bool IsSatisfied = SemaRef.CheckConstraintSatisfaction(

应该是这样的:

        bool HadError = SemaRef.CheckConstraintSatisfaction(
            ...
        );

        if (HadError) {
            llvm::outs() << "CheckConstraintSatisfaction reported an error.\n";
            return;
        }

我不确定此调用到底是什么构成错误。 在我的 测试,

HadError
始终为假。

然后,要检查是否满意,请查看 的

IsSatisfied
标志
Satisfaction
对象:

        if (Satisfaction.IsSatisfied) {
            llvm::outs() << "The class " << Class->getName() << " satisfies the concept " << Concept->getName() << ".\n";
        } else {
            llvm::outs() << "The class " << Class->getName() << " does NOT satisfy the concept " << Concept->getName() << ".\n";
        }

您在评论中说您尝试过这样做;其他问题可能被掩盖 这个修复。

问题:构造一个
TypeTraitExpr

如果是

TypeRequirement
, 原始代码有一个被注释掉的尝试来创建一个
TypeTraitExpr
然后可以传递给
CheckConstraintSatisfaction
:

                if (Type->isDependentType()) {
                    std::cout << "--- CheckConceptUsage isDependentType" << std::endl;
                    // Create a pseudo-expression that checks if this type exists
                    // TypeTraitExpr *TraitExpr = TypeTraitExpr::Create(
                    //     SemaRef.Context, 
                    //     DependentType,
                    //     SourceLocation(), 
                    //     UTT_IsCompleteType,  // Use a type trait like "is complete type"
                    //     ArrayRef<QualType>(DependentType),
                    //     SourceLocation(), 
                    //     SemaRef.Context.BoolTy
                    // );
                    
                    // ConstraintExprs.push_back(TraitExpr);
                }

由于这已被注释掉,并且该示例使用两种类型 要求,

IsSatisfied
标志始终为真,因为没有 约束已传递给它。

取消注释代码后,并没有编译,而是一些小 更改足以修复它:

                if (Type->isDependentType()) {
                    std::cout << "--- CheckConceptUsage isDependentType" << std::endl;
                    // Create a pseudo-expression that checks if this type exists
                    TypeTraitExpr *TraitExpr = TypeTraitExpr::Create(
                        SemaRef.Context, 
                        DependentType,
                        SourceLocation(), 
                        UTT_IsCompleteType,  // Use a type trait like "is complete type"
                        ArrayRef<TypeSourceInfo*>(TypeReq->getType()),
                        SourceLocation(), 
                        false /* Value; meaning is unclear */
                    );
                    
                    ConstraintExprs.push_back(TraitExpr);
                }

具体:

  • 我们需要传递一个数组

    TypeSourceInfo
    , 不是
    QualType
    。 前者具有源位置信息和其他相关详细信息 类型描述的语法出现,而后者是 只是抽象语义类型。
    TypeSourceInfo
    就在那里 在
    TypeRequirement
    ;原来的代码是出于某种原因 绕过它以获得
    QualType

  • 最后一个参数是一个普通的

    bool
    ,而不是一个类型(这就是
    BoolTy
    是)。 这是什么意思? 追踪代码,最终结果是
    TypeTraitExprBitFields.Value
    ,其评论是“如果这个表达式 不依赖于值,这表明该特征是否被评估 真还是假。”我认为这在这里无关紧要,因为我们还没有 尚未评估特征表达,在我的实验中它没有产生 我传递的值不同。

话虽如此,我实际上不确定构建一个

TypeTraitExpr
确实是正确的方法,因为它看起来相当 令人费解。 但它有效,至少对于这些简单的情况,所以我没有 调查是否有更好的替代方案。

问题:只检查部分课程?

原始代码仅检查

SimpleClass
HasValueType
。 这使得很难判断它是否有效,因为代码应该 认识到
SimpleClass
确实满足约束条件,但
X
确实满足 不是。 所以我添加了一个简单的嵌套循环来检查所有对:

        std::cout << "Testing all classes against all concepts.\n";
        for (auto const &kv1 : ConceptMap) {
            ConceptDecl const *concept = kv1.second;
            std::cout << "- concept " << concept->getNameAsString() << ":\n";

            for (auto const &kv2 : ClassMap) {
                clang::CXXRecordDecl const *clazz = kv2.second;
                std::cout << "-- class " << clazz->getNameAsString() << ":\n";

                CheckConceptUsage(SemaRef, concept, clazz);
            }
        }

修复后的输出

通过上述修复,程序可以在

example.cpp
上运行 链接要点(它应该直接在问题中):

template<typename T>
concept HasValueType = requires {
    typename T::value_type;
    typename T::test;
};

template <class myType>
myType GetMax (myType a, int b) {
 return (a>b?a:b);
}

template<class T>
class A {
    A(){}

    using value_type = int;

    int func_A1(T a, int b) {
        return 5;
    }
};

class SimpleClass {
public:
    using value_type = int;  // This is the nested type alias that satisfies the concept
    using test = float;
};

template<class T>
class B {
    B(){}

    int func_A1(T a, int b) {
        return 5;
    }
};

class X{};

float test(A<float> x, float b, A<B<int>> y) {
return 4.0;
}

template<class T>
float test2(A<T> x, float b, A<B<T>> y) {
return 4.0;
}

int main() {
    return 0;
}

example.cpp
的输出摘录:

...
The class SimpleClass satisfies the concept HasValueType.
...
The class X does NOT satisfy the concept HasValueType.

该输出是正确的,因为

SimpleClass
value_type
test
成员类型,而
X
则不然。

我也测试了这个输入,

example2.cpp

// Simple concept that requires an `asString` method.
template <typename T>
concept HasAsString = requires(T v)
{
  v.asString();
};


// Does not have `asString`.
struct A {};

// Has `asString`.
struct B {
  void asString();
};


// Requires the concept to be satisfied.
template <typename T>
  requires HasAsString<T>
struct S {};


// Does not compile due to unsatisfied constraint.
//S<A> sa;

static_assert(!HasAsString<A>);


// Compiles since the constraint is satisfied.
S<B> sb;

static_assert(HasAsString<B>);

其输出摘录为:

...
The class A does NOT satisfy the concept HasAsString.
...
The class B satisfies the concept HasAsString.

这也是正确的,因为只有

B
asString()

完整程序

这是链接要点中的完整程序,并应用了修复:

#include <clang/Tooling/CommonOptionsParser.h>
#include <clang/Tooling/Tooling.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Frontend/FrontendActions.h>
#include <clang/AST/ASTConsumer.h>
#include <clang/AST/RecursiveASTVisitor.h>
#include <clang/AST/Decl.h>
#include <clang/AST/DeclTemplate.h>
#include <llvm/Support/CommandLine.h>
#include <clang/Sema/Sema.h>
#include <clang/Sema/Template.h>
#include <clang/AST/ASTContext.h>

#include <iostream>


using namespace clang;
using namespace clang::tooling;
using namespace llvm;

// Define the option category for command-line options
static llvm::cl::OptionCategory MyToolCategory("my-tool options");

std::map<std::string, const CXXRecordDecl*> ClassMap;
std::map<std::string, const ConceptDecl*> ConceptMap;

void CheckConceptUsage(Sema &SemaRef, const ConceptDecl *Concept, const CXXRecordDecl *Class) {
    // Get the type of the class.
    QualType ClassType = SemaRef.Context.getRecordType(Class);

    // Create a TemplateArgument representing the class type.
    TemplateArgument ClassTemplateArg(ClassType);

    // Prepare the necessary data structures for the constraint check.
    ConstraintSatisfaction Satisfaction;

    // Create a MultiLevelTemplateArgumentList
    MultiLevelTemplateArgumentList TemplateArgs;
     ArrayRef<TemplateArgument> TemplateArgsRef(ClassTemplateArg);
    TemplateArgs.addOuterTemplateArguments(const_cast<CXXRecordDecl *>(Class), TemplateArgsRef, /*Final*/ true);
    // TemplateArgs.addOuterTemplateArguments(ArrayRef<TemplateArgument>(ClassTemplateArg));


    // Retrieve the constraint expression associated with the concept
    const Expr *ConstraintExpr = Concept->getConstraintExpr();
    
    if (!ConstraintExpr) {
        llvm::outs() << "The concept " << Concept->getNameAsString() 
                     << " has no constraints (requires clause) to check.\n";
        return;
    }

    // Cast the constraint expression to RequiresExpr to access its components
    if (const RequiresExpr *ReqExpr = llvm::dyn_cast<RequiresExpr>(ConstraintExpr)) {
        std::cout << "--- CheckConceptUsage if " << std::endl;
        // Get the list of requirements (constraints) in the requires expression
        llvm::SmallVector<const Expr*, 4> ConstraintExprs;

        for (const auto &Requirement : ReqExpr->getRequirements()) {
            std::cout << "--- CheckConceptUsage for " << std::endl;
            if (const auto *ExprReq = llvm::dyn_cast<clang::concepts::ExprRequirement>(Requirement)) {
                // Handle expression requirements
                std::cout << "--- CheckConceptUsage ExprRequirement" << std::endl;
                ConstraintExprs.push_back(ExprReq->getExpr());
            } else if (const auto *TypeReq = llvm::dyn_cast<clang::concepts::TypeRequirement>(Requirement)) {
                // Handle type requirements by evaluating the type's instantiation dependency
                std::cout << "--- CheckConceptUsage TypeRequirement" << std::endl;
                QualType Type = TypeReq->getType()->getType();
                QualType DependentType = TypeReq->getType()->getType();
                if (Type->isDependentType()) {
                    std::cout << "--- CheckConceptUsage isDependentType" << std::endl;
                    // Create a pseudo-expression that checks if this type exists
                    TypeTraitExpr *TraitExpr = TypeTraitExpr::Create(
                        SemaRef.Context, 
                        DependentType,
                        SourceLocation(), 
                        UTT_IsCompleteType,  // Use a type trait like "is complete type"
                        ArrayRef<TypeSourceInfo*>(TypeReq->getType()),
                        SourceLocation(), 
                        false /* Value; meaning is unclear */
                    );
                    
                    ConstraintExprs.push_back(TraitExpr);
                }
            }
        }

        
        std::cout << "--- CheckConceptUsage ConstraintExprs size:" << ConstraintExprs.size() << std::endl;

        // Now use the updated list of constraints in the satisfaction check
        //
        // Quoting the documentation of the return value:
        //
        //   "true if an error occurred and satisfaction could not be
        //   checked, false otherwise."
        //
        bool HadError = SemaRef.CheckConstraintSatisfaction(
            Concept, 
            ConstraintExprs, 
            TemplateArgs, 
            Class->getSourceRange(), 
            Satisfaction
        );

        if (HadError) {
            llvm::outs() << "CheckConstraintSatisfaction reported an error.\n";
            return;
        }

        llvm::outs() << "ContainsErrors: " << Satisfaction.ContainsErrors << "\n";

        if (Satisfaction.IsSatisfied) {
            llvm::outs() << "The class " << Class->getName() << " satisfies the concept " << Concept->getName() << ".\n";
        } else {
            llvm::outs() << "The class " << Class->getName() << " does NOT satisfy the concept " << Concept->getName() << ".\n";
        }
    } else {
        llvm::outs() << "The concept " << Concept->getNameAsString() 
                     << " does not have a valid requires expression.\n";
    }
}

class FunctionConsumer : public ASTConsumer {
public:
    explicit FunctionConsumer(CompilerInstance &CI): CI(CI) {}

    virtual void HandleTranslationUnit(ASTContext &Context) override {
        TranslationUnitDecl *TU = Context.getTranslationUnitDecl();
        this->Context = &Context;
        Sema &SemaRef = CI.getSema();

        for (Decl *D : TU->decls()) {
            printDeclKind(D);
            if (CXXRecordDecl *CRD = llvm::dyn_cast<CXXRecordDecl>(D)) {
                // Handle classes/structs and their methods
                handleClass(CRD);
            } else if (ConceptDecl *CD = llvm::dyn_cast<ConceptDecl>(D)) {
                // Handle template classes
                std::cout << "--- CONCEPT: " << std::endl;
                printConceptDetails(CD, &Context);
                std::cout << "--- END CONCEPT: " << std::endl;
            } 
        }

        // After collecting all declarations, test a class against a concept
        auto ConceptIter = ConceptMap.find("HasValueType"); // Replace with actual concept name
        auto ClassIter = ClassMap.find("SimpleClass"); // Replace with actual class name
        if (ConceptIter != ConceptMap.end() && ClassIter != ClassMap.end()) {
            CheckConceptUsage(SemaRef, ConceptIter->second, ClassIter->second);
        }

        std::cout << "Testing all classes against all concepts.\n";
        for (auto const &kv1 : ConceptMap) {
            ConceptDecl const *concept = kv1.second;
            std::cout << "- concept " << concept->getNameAsString() << ":\n";

            for (auto const &kv2 : ClassMap) {
                clang::CXXRecordDecl const *clazz = kv2.second;
                std::cout << "-- class " << clazz->getNameAsString() << ":\n";

                CheckConceptUsage(SemaRef, concept, clazz);
            }
        }
    }

private:
    ASTContext *Context;
    CompilerInstance &CI;

    void printDeclKind(Decl *D) {
        std::cout << "Declaration Kind: " << D->getDeclKindName() << std::endl;
    }

    void printConceptDetails(const clang::ConceptDecl *CD, clang::ASTContext *Context) {
        std::cout << "Concept: " << CD->getNameAsString() << std::endl;
        ConceptMap[CD->getNameAsString()] = CD;
        std::cout << std::endl;
    }


    void handleClass(CXXRecordDecl *CRD) {
        if (!CRD->isThisDeclarationADefinition()) {
            return; // Skip forward declarations
        }

        std::cout << "Class: " << CRD->getNameAsString() << std::endl;

        // Store the class in the map
        ClassMap[CRD->getNameAsString()] = CRD;
    }
};

class FindFunctionsAction : public ASTFrontendAction {
public:
    std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef file) override {
        return std::make_unique<FunctionConsumer>(CI);
    }
};

int main(int argc, const char **argv) {
    auto ExpectedParser = CommonOptionsParser::create(argc, argv, MyToolCategory);
    if (!ExpectedParser) {
        llvm::errs() << ExpectedParser.takeError();
        return 1;
    }
    CommonOptionsParser& OptionsParser = ExpectedParser.get();
    ClangTool Tool(OptionsParser.getCompilations(), OptionsParser.getSourcePathList());

    // Manually add the required C++ standard flag
    std::vector<std::string> CompileFlags = {"-std=c++20"};
    Tool.appendArgumentsAdjuster(getInsertArgumentAdjuster(CompileFlags, ArgumentInsertPosition::BEGIN));

    return Tool.run(newFrontendActionFactory<FindFunctionsAction>().get());
}

为了完整起见,我的所有测试都是使用 Clang 16.0.0 执行的。

© www.soinside.com 2019 - 2024. All rights reserved.