Использование библиотеки Clang: как проверить, соответствует ли класс концепции

У меня есть небольшой код, который анализирует объявления 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

Ожидаете ли вы, что люди будут отлаживать вашу программу за вас?

Gene 25.08.2024 18:54

Думаю, мой вопрос был достаточно ясен. Это не программа, это просто MRE, помогающий запустить метод, отлаживать нечего. Способны ли вы понять разницу между настоящей программой и MRE?

Alex 25.08.2024 19:12

Вы уверены, что правильно интерпретируете возвращаемое значение CheckConstraintSatisfaction? согласно документации, он «возвращает true, если произошла ошибка и удовлетворение не может быть проверено, в противном случае — false» — так что false, вероятно, просто означает, что проверка ограничения прошла успешно без ошибок, и фактический результат (если ограничение удовлетворено или нет) равен в параметре ConstraintSatisfaction out.

Turtlefight 28.08.2024 11:23

@Turtlefight, я уже пытался использовать if (Satisfaction.IsSatisfied) {... но это тоже не сработало.

Alex 28.08.2024 13:56
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
4
117
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Исходный код в суть очень близко к работе. Я смог заставить его работать на заданном пример и еще один простой, с некоторыми изменениями.

Проблема: CheckConstraintSatisfaction возвращаемое значение

Sema::CheckConstraintSatisfaction возвращает:

true, если произошла ошибка и удовлетворение не удалось проверить, в противном случае — false.

То есть он указывает, может ли быть выполнена проверка удовлетворенности, а не были ли выполнены ограничения.

Следовательно, строка:

        bool IsSatisfied = SemaRef.CheckConstraintSatisfaction(

вместо этого должно быть что-то вроде:

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

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

Я не уверен, что именно представляет собой ошибку для этого вызова. В моем тестах, HadError всегда было ложным.

Затем, чтобы проверить удовлетворенность, посмотрите на флаг IsSatisfiedSatisfaction объект:

        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

В случае ТипТребования , в исходном коде была закомментированная попытка создать 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.

Опять же, это правильно, как и только у BasString().

Полная программа

Вот полная программа из связанного текста с внесенными исправлениями:

#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.

Другие вопросы по теме