我正在写(我的第一张)整洁的支票。它应该检测宏何时定义类型,例如:
#define MY_INT int
#define MY_STRING std::string
class MyClass {}
#define MY_CLASS MyClass
#define MY_INT_ARRAY std::array<int>
template <typename T> class MyTemplateClass {};
#define MY_TEMPLATE_TYPE MyTemplateClass<int>
目标是标记定义类型的宏,并建议使用
typedef
或 using
代替。
我开始使用现有的
cppcoreguidelines-macro-usage
并将其剥离,仅通过 registerPPCallbacks
获取现有的宏,效果很好。因为我发现我需要一个 ASTContext 来处理类型信息,所以我还通过 registerMatchers(MatchFinder *Finder)
注册了一些随机 Matcher,所以我能够从 ASTContext
获取 Result.Context
。
现在可以使用以下方式检测用户定义的类型,例如
MyClass
:
IdentifierInfo &II = Context->Idents.get(Identifier);
DeclContext::lookup_result Result = Context->getTranslationUnitDecl()->lookup(&II);
// Iterate through the lookup result to find a TypeDecl
for (NamedDecl *ND : Result) {
if (TypeDecl *TD = dyn_cast<TypeDecl>(ND)) {
return Context->getTypeDeclType(TD); // User-defined type!
}
}
不幸的是,这种方法对于内置类型(
int
,uint8_t
)和标准类型(std::string
,std::array<int>
)失败,其中查找结果为空。对于此类类型,尝试仅使用字符串比较,但我觉得这是一种非常脆弱的方法:
if (Identifier == "int") return Context->IntTy;
if (Identifier == "float") return Context->FloatTy;
对于更高级的类型,例如提到的模板类或数组,我没有想法......
是否有一种可靠的方法来检测宏是否定义了类型(尤其是内置类型、标准类型和模板类型)。关于改进这种方法或其他替代方案有什么建议吗?
是否有一种可靠的方法来检测宏是否定义了类型?
没有。
问题在于宏定义处的查找上下文与其扩展处的查找上下文不同。 因此,不可能可靠地预测宏定义中的标记将如何解释。
考虑:
#define MYCLASS myclass
class myclass {};
在定义站点查找将找不到其后面的声明。
考虑:
namespace NS {
class myclass {};
}
using namespace NS;
#define MYCLASS myclass
在全局范围(此处是宏出现的位置)中对
myclass
的简单查找将错过 NS::myclass
。 如果该指令出现在宏定义之后,则考虑到 using
指令的更复杂的查找将会失败。
与此相关,宏定义不需要与其扩展出现在相同的作用域中,因此在其定义作用域中查找它并不一定是正确的。
考虑:
template <typename T> class myclass {};
#define MYCLASS_OF_MYINT myclass<MYINT>
#define MYINT int
如果我们将文本
myclass<MYINT>
提供给解析器,在定义点的适当范围内,它无法解析,因为 MYINT
需要扩展,但该宏尚未定义。 如果我们记住在翻译单元末尾有效的所有宏定义,并在感兴趣的定义中扩展它们的出现,我们就会遇到重新定义(这种情况很少见,但问题指定了“稳健”)。
考虑:
#define MYCLASS myclass
namespace NS1 {
class myclass {};
MYCLASS x; // Expands to a type.
}
namespace NS2 {
int myclass;
int f()
{
return MYCLASS; // Expands to a variable.
}
}
宏被扩展两次,但该扩展仅在一个地方被视为一种类型。 (是的,这是病态的。)
检查宏扩展位点而不是检查宏定义是可行的。 具体来说,可以使用
RecursiveASTVisitor
搜索所有出现的 TypeLoc
(代表类型的语法出现),然后,对于每一个,调用 getSourceRange()
,并检查“拼写”是否正确。第一个和最后一个标记的位置对应于某些宏定义的第一个和最后一个标记。 可以使用 PPCallbacks
来记录宏定义(问题表明您已经在这样做)。
这种方法的缺点是它只会报告在正在分析的翻译单元中某处扩展的宏。 但它避免了无法孤立解释宏定义的问题。