llvm-project/clang/lib/Tooling/DumpTool/generate_cxx_src_locs.py
Stephen Kelly dd68942f1d [AST] Add TypeLoc support to node introspection
Extend the matchers gathering API for types to record template
parameters.  The TypeLoc type hierarchy has some types which are
templates used in CRTP such as PointerLikeTypeLoc.  Record the inherited
template and template arguments of types inheriting those CRTP types in
the ClassInheritance map.  Because the name inherited from is now
computed, the value type in that map changes from StringRef to
std::string.  This also causes the toJSON override signature used to
serialize that map to change.

Remove the logic for skipping over empty ClassData instances.  Several
classes such as TypeOfExprTypeLoc inherit a CRTP class which provides
interesting locations though the derived class does not.  Record it as a
class to make the locations it inherits available.

Record the typeSourceInfo accessors too as they provide access to
TypeLocs in many classes.

The existing unit tests use UnorderedElementsAre to compare the
introspection result with the expected result.  Our current
implementation of google mock (in gmock-generated-matchers.h) is limited
to support for comparing a container of 10 elements.  As we are now
returning more than 10 results for one of the introspection tests,
change it to instead compare against an ordered vector of pairs.

Because a macro is used to generate API strings and API calls, disable
clang-format in blocks of expected results.  Otherwise clang-format
would insert whitespaces which would then be compared against the
introspected strings and fail the test.

Introduce a recursion guard in the generated code.  The TypeLoc class
has IgnoreParens() API which by default returns itself, so it would
otherwise recurse infinitely.

Differential Revision: https://reviews.llvm.org/D100516
2021-04-17 22:58:02 +01:00

423 lines
14 KiB
Python
Executable File

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import json
import argparse
class Generator(object):
implementationContent = ''
def __init__(self, templateClasses):
self.templateClasses = templateClasses
def GeneratePrologue(self):
self.implementationContent += \
"""
/*===- Generated file -------------------------------------------*- C++ -*-===*\
|* *|
|* Introspection of available AST node SourceLocations *|
|* *|
|* Automatically generated file, do not edit! *|
|* *|
\*===----------------------------------------------------------------------===*/
namespace clang {
namespace tooling {
using LocationAndString = SourceLocationMap::value_type;
using RangeAndString = SourceRangeMap::value_type;
bool NodeIntrospection::hasIntrospectionSupport() { return true; }
struct RecursionPopper
{
RecursionPopper(std::vector<clang::TypeLoc> &TypeLocRecursionGuard)
: TLRG(TypeLocRecursionGuard)
{
}
~RecursionPopper()
{
TLRG.pop_back();
}
private:
std::vector<clang::TypeLoc> &TLRG;
};
"""
def GenerateBaseGetLocationsDeclaration(self, CladeName):
InstanceDecoration = "*"
if CladeName == "TypeLoc":
InstanceDecoration = "&"
self.implementationContent += \
"""
void GetLocationsImpl(SharedLocationCall const& Prefix,
clang::{0} const {1}Object, SourceLocationMap &Locs,
SourceRangeMap &Rngs,
std::vector<clang::TypeLoc> &TypeLocRecursionGuard);
""".format(CladeName, InstanceDecoration)
def GenerateSrcLocMethod(self,
ClassName, ClassData, CreateLocalRecursionGuard):
NormalClassName = ClassName
RecursionGuardParam = ('' if CreateLocalRecursionGuard else \
', std::vector<clang::TypeLoc>& TypeLocRecursionGuard')
if "templateParms" in ClassData:
TemplatePreamble = "template <typename "
ClassName += "<"
First = True
for TA in ClassData["templateParms"]:
if not First:
ClassName += ", "
TemplatePreamble += ", typename "
First = False
ClassName += TA
TemplatePreamble += TA
ClassName += ">"
TemplatePreamble += ">\n";
self.implementationContent += TemplatePreamble
self.implementationContent += \
"""
static void GetLocations{0}(SharedLocationCall const& Prefix,
clang::{1} const &Object,
SourceLocationMap &Locs, SourceRangeMap &Rngs {2})
{{
""".format(NormalClassName, ClassName, RecursionGuardParam)
if 'sourceLocations' in ClassData:
for locName in ClassData['sourceLocations']:
self.implementationContent += \
"""
Locs.insert(LocationAndString(Object.{0}(),
llvm::makeIntrusiveRefCnt<LocationCall>(Prefix, "{0}")));
""".format(locName)
self.implementationContent += '\n'
if 'sourceRanges' in ClassData:
for rngName in ClassData['sourceRanges']:
self.implementationContent += \
"""
Rngs.insert(RangeAndString(Object.{0}(),
llvm::makeIntrusiveRefCnt<LocationCall>(Prefix, "{0}")));
""".format(rngName)
self.implementationContent += '\n'
if 'typeLocs' in ClassData or 'typeSourceInfos' in ClassData:
if CreateLocalRecursionGuard:
self.implementationContent += \
'std::vector<clang::TypeLoc> TypeLocRecursionGuard;\n'
self.implementationContent += '\n'
if 'typeLocs' in ClassData:
for typeLoc in ClassData['typeLocs']:
self.implementationContent += \
"""
if (Object.{0}()) {{
GetLocationsImpl(
llvm::makeIntrusiveRefCnt<LocationCall>(Prefix, "{0}"),
Object.{0}(), Locs, Rngs, TypeLocRecursionGuard);
}}
""".format(typeLoc)
self.implementationContent += '\n'
if 'typeSourceInfos' in ClassData:
for tsi in ClassData['typeSourceInfos']:
self.implementationContent += \
"""
if (Object.{0}()) {{
GetLocationsImpl(llvm::makeIntrusiveRefCnt<LocationCall>(
llvm::makeIntrusiveRefCnt<LocationCall>(Prefix, "{0}",
LocationCall::ReturnsPointer), "getTypeLoc"),
Object.{0}()->getTypeLoc(), Locs, Rngs, TypeLocRecursionGuard);
}}
""".format(tsi)
self.implementationContent += '\n'
self.implementationContent += '}\n'
def GenerateFiles(self, OutputFile):
with open(os.path.join(os.getcwd(),
OutputFile), 'w') as f:
f.write(self.implementationContent)
def GenerateBaseGetLocationsFunction(self, ASTClassNames,
ClassEntries, CladeName, InheritanceMap,
CreateLocalRecursionGuard):
MethodReturnType = 'NodeLocationAccessors'
InstanceDecoration = "*"
if CladeName == "TypeLoc":
InstanceDecoration = "&"
Signature = \
'GetLocations(clang::{0} const {1}Object)'.format(
CladeName, InstanceDecoration)
ImplSignature = \
"""
GetLocationsImpl(SharedLocationCall const& Prefix,
clang::{0} const {1}Object, SourceLocationMap &Locs,
SourceRangeMap &Rngs,
std::vector<clang::TypeLoc> &TypeLocRecursionGuard)
""".format(CladeName, InstanceDecoration)
self.implementationContent += 'void {0} {{ '.format(ImplSignature)
if CladeName == "TypeLoc":
self.implementationContent += 'if (Object.isNull()) return;'
self.implementationContent += \
"""
if (llvm::find(TypeLocRecursionGuard, Object) != TypeLocRecursionGuard.end())
return;
TypeLocRecursionGuard.push_back(Object);
RecursionPopper RAII(TypeLocRecursionGuard);
"""
RecursionGuardParam = ''
if not CreateLocalRecursionGuard:
RecursionGuardParam = ', TypeLocRecursionGuard'
ArgPrefix = '*'
if CladeName == "TypeLoc":
ArgPrefix = ''
self.implementationContent += \
'GetLocations{0}(Prefix, {1}Object, Locs, Rngs {2});'.format(
CladeName, ArgPrefix, RecursionGuardParam)
if CladeName == "TypeLoc":
self.implementationContent += \
'''
if (auto QTL = Object.getAs<clang::QualifiedTypeLoc>()) {
auto Dequalified = QTL.getNextTypeLoc();
return GetLocationsImpl(llvm::makeIntrusiveRefCnt<LocationCall>(Prefix, "getNextTypeLoc"),
Dequalified,
Locs,
Rngs,
TypeLocRecursionGuard);
}'''
for ASTClassName in ASTClassNames:
if ASTClassName in self.templateClasses:
continue
if ASTClassName == CladeName:
continue
if CladeName != "TypeLoc":
self.implementationContent += \
"""
if (auto Derived = llvm::dyn_cast<clang::{0}>(Object)) {{
GetLocations{0}(Prefix, *Derived, Locs, Rngs {1});
}}
""".format(ASTClassName, RecursionGuardParam)
continue
self.GenerateBaseTypeLocVisit(ASTClassName, ClassEntries,
RecursionGuardParam, InheritanceMap)
self.implementationContent += '}'
self.implementationContent += \
"""
{0} NodeIntrospection::{1} {{
NodeLocationAccessors Result;
SharedLocationCall Prefix;
std::vector<clang::TypeLoc> TypeLocRecursionGuard;
GetLocationsImpl(Prefix, Object, Result.LocationAccessors,
Result.RangeAccessors, TypeLocRecursionGuard);
""".format(MethodReturnType, Signature)
self.implementationContent += 'return Result; }'
def GenerateBaseTypeLocVisit(self, ASTClassName, ClassEntries,
RecursionGuardParam, InheritanceMap):
CallPrefix = 'Prefix'
if ASTClassName != 'TypeLoc':
CallPrefix = \
'''llvm::makeIntrusiveRefCnt<LocationCall>(Prefix,
"getAs<clang::{0}>", LocationCall::IsCast)
'''.format(ASTClassName)
if ASTClassName in ClassEntries:
self.implementationContent += \
"""
if (auto ConcreteTL = Object.getAs<clang::{0}>())
GetLocations{1}({2}, ConcreteTL, Locs, Rngs {3});
""".format(ASTClassName, ASTClassName,
CallPrefix, RecursionGuardParam)
if ASTClassName in InheritanceMap:
for baseTemplate in self.templateClasses:
if baseTemplate in InheritanceMap[ASTClassName]:
self.implementationContent += \
"""
if (auto ConcreteTL = Object.getAs<clang::{0}>())
GetLocations{1}({2}, ConcreteTL, Locs, Rngs {3});
""".format(InheritanceMap[ASTClassName], baseTemplate,
CallPrefix, RecursionGuardParam)
def GenerateDynNodeVisitor(self, CladeNames):
MethodReturnType = 'NodeLocationAccessors'
Signature = \
'GetLocations(clang::DynTypedNode const &Node)'
self.implementationContent += MethodReturnType \
+ ' NodeIntrospection::' + Signature + '{'
for CladeName in CladeNames:
self.implementationContent += \
"""
if (const auto *N = Node.get<{0}>())
""".format(CladeName)
ArgPrefix = ""
if CladeName == "TypeLoc":
ArgPrefix = "*"
self.implementationContent += \
"""
return GetLocations({0}const_cast<{1} *>(N));""".format(ArgPrefix, CladeName)
self.implementationContent += '\nreturn {}; }'
def GenerateEpilogue(self):
self.implementationContent += '''
}
}
'''
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--json-input-path',
help='Read API description from FILE', metavar='FILE')
parser.add_argument('--output-file', help='Generate output in FILEPATH',
metavar='FILEPATH')
parser.add_argument('--empty-implementation',
help='Generate empty implementation',
action="store", type=int)
options = parser.parse_args()
use_empty_implementation = options.empty_implementation
if (not use_empty_implementation
and not os.path.exists(options.json_input_path)):
use_empty_implementation = True
if not use_empty_implementation:
with open(options.json_input_path) as f:
jsonData = json.load(f)
if not 'classesInClade' in jsonData or not jsonData["classesInClade"]:
use_empty_implementation = True
if use_empty_implementation:
with open(os.path.join(os.getcwd(),
options.output_file), 'w') as f:
f.write("""
namespace clang {
namespace tooling {
bool NodeIntrospection::hasIntrospectionSupport() { return false; }
NodeLocationAccessors NodeIntrospection::GetLocations(clang::Stmt const *) {
return {};
}
NodeLocationAccessors NodeIntrospection::GetLocations(clang::Decl const *) {
return {};
}
NodeLocationAccessors NodeIntrospection::GetLocations(
clang::CXXCtorInitializer const *) {
return {};
}
NodeLocationAccessors NodeIntrospection::GetLocations(
clang::NestedNameSpecifierLoc const*) {
return {};
}
NodeLocationAccessors NodeIntrospection::GetLocations(
clang::TemplateArgumentLoc const*) {
return {};
}
NodeLocationAccessors NodeIntrospection::GetLocations(
clang::CXXBaseSpecifier const*) {
return {};
}
NodeLocationAccessors NodeIntrospection::GetLocations(
clang::TypeLoc const&) {
return {};
}
NodeLocationAccessors
NodeIntrospection::GetLocations(clang::DynTypedNode const &) {
return {};
}
} // namespace tooling
} // namespace clang
""")
sys.exit(0)
templateClasses = []
for (ClassName, ClassAccessors) in jsonData['classEntries'].items():
if "templateParms" in ClassAccessors:
templateClasses.append(ClassName)
g = Generator(templateClasses)
g.GeneratePrologue()
for (CladeName, ClassNameData) in jsonData['classesInClade'].items():
g.GenerateBaseGetLocationsDeclaration(CladeName)
def getCladeName(ClassName):
for (CladeName, ClassNameData) in jsonData['classesInClade'].items():
if ClassName in ClassNameData:
return CladeName
for (ClassName, ClassAccessors) in jsonData['classEntries'].items():
cladeName = getCladeName(ClassName)
g.GenerateSrcLocMethod(
ClassName, ClassAccessors,
cladeName not in [
'NestedNameSpecifierLoc',
'TemplateArgumentLoc',
'TypeLoc'])
for (CladeName, ClassNameData) in jsonData['classesInClade'].items():
g.GenerateBaseGetLocationsFunction(
ClassNameData,
jsonData['classEntries'],
CladeName,
jsonData["classInheritance"],
CladeName not in [
'NestedNameSpecifierLoc',
'TemplateArgumentLoc',
'TypeLoc'])
g.GenerateDynNodeVisitor(jsonData['classesInClade'].keys())
g.GenerateEpilogue()
g.GenerateFiles(options.output_file)
if __name__ == '__main__':
main()