//=== ClangASTPropsEmitter.cpp - Generate Clang AST properties --*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This tablegen backend emits code for working with Clang AST properties. // //===----------------------------------------------------------------------===// #include "ASTTableGen.h" #include "TableGenBackends.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/Twine.h" #include "llvm/TableGen/Error.h" #include "llvm/TableGen/Record.h" #include "llvm/TableGen/TableGenBackend.h" #include #include #include #include using namespace llvm; using namespace clang; using namespace clang::tblgen; static StringRef getReaderResultType(TypeNode _) { return "QualType"; } namespace { struct ReaderWriterInfo { bool IsReader; /// The name of the node hierarchy. Not actually sensitive to IsReader, /// but useful to cache here anyway. StringRef HierarchyName; /// The suffix on classes: Reader/Writer StringRef ClassSuffix; /// The base name of methods: read/write StringRef MethodPrefix; /// The name of the property helper member: R/W StringRef HelperVariable; /// The result type of methods on the class. StringRef ResultType; template static ReaderWriterInfo forReader() { return ReaderWriterInfo{ true, NodeClass::getASTHierarchyName(), "Reader", "read", "R", getReaderResultType(NodeClass()) }; } template static ReaderWriterInfo forWriter() { return ReaderWriterInfo{ false, NodeClass::getASTHierarchyName(), "Writer", "write", "W", "void" }; } }; struct NodeInfo { std::vector Properties; CreationRule Creator = nullptr; OverrideRule Override = nullptr; ReadHelperRule ReadHelper = nullptr; }; struct CasedTypeInfo { TypeKindRule KindRule; std::vector Cases; }; class ASTPropsEmitter { raw_ostream &Out; RecordKeeper &Records; std::map NodeInfos; std::vector AllPropertyTypes; std::map CasedTypeInfos; public: ASTPropsEmitter(RecordKeeper &records, raw_ostream &out) : Out(out), Records(records) { // Find all the properties. for (Property property : records.getAllDerivedDefinitions(PropertyClassName)) { HasProperties node = property.getClass(); NodeInfos[node].Properties.push_back(property); } // Find all the creation rules. for (CreationRule creationRule : records.getAllDerivedDefinitions(CreationRuleClassName)) { HasProperties node = creationRule.getClass(); auto &info = NodeInfos[node]; if (info.Creator) { PrintFatalError(creationRule.getLoc(), "multiple creator rules for \"" + node.getName() + "\""); } info.Creator = creationRule; } // Find all the override rules. for (OverrideRule overrideRule : records.getAllDerivedDefinitions(OverrideRuleClassName)) { HasProperties node = overrideRule.getClass(); auto &info = NodeInfos[node]; if (info.Override) { PrintFatalError(overrideRule.getLoc(), "multiple override rules for \"" + node.getName() + "\""); } info.Override = overrideRule; } // Find all the write helper rules. for (ReadHelperRule helperRule : records.getAllDerivedDefinitions(ReadHelperRuleClassName)) { HasProperties node = helperRule.getClass(); auto &info = NodeInfos[node]; if (info.ReadHelper) { PrintFatalError(helperRule.getLoc(), "multiple write helper rules for \"" + node.getName() + "\""); } info.ReadHelper = helperRule; } // Find all the concrete property types. for (PropertyType type : records.getAllDerivedDefinitions(PropertyTypeClassName)) { // Ignore generic specializations; they're generally not useful when // emitting basic emitters etc. if (type.isGenericSpecialization()) continue; AllPropertyTypes.push_back(type); } // Find all the type kind rules. for (TypeKindRule kindRule : records.getAllDerivedDefinitions(TypeKindClassName)) { PropertyType type = kindRule.getParentType(); auto &info = CasedTypeInfos[type]; if (info.KindRule) { PrintFatalError(kindRule.getLoc(), "multiple kind rules for \"" + type.getCXXTypeName() + "\""); } info.KindRule = kindRule; } // Find all the type cases. for (TypeCase typeCase : records.getAllDerivedDefinitions(TypeCaseClassName)) { CasedTypeInfos[typeCase.getParentType()].Cases.push_back(typeCase); } Validator(*this).validate(); } void visitAllProperties(HasProperties derived, const NodeInfo &derivedInfo, function_ref visit) { std::set ignoredProperties; auto overrideRule = derivedInfo.Override; if (overrideRule) { auto list = overrideRule.getIgnoredProperties(); ignoredProperties.insert(list.begin(), list.end()); } // TODO: we should sort the properties in various ways // - put arrays at the end to enable abbreviations // - put conditional properties after properties used in the condition visitAllNodesWithInfo(derived, derivedInfo, [&](HasProperties node, const NodeInfo &info) { for (Property prop : info.Properties) { if (ignoredProperties.count(prop.getName())) continue; visit(prop); } }); } void visitAllNodesWithInfo(HasProperties derivedNode, const NodeInfo &derivedNodeInfo, llvm::function_ref visit) { visit(derivedNode, derivedNodeInfo); // Also walk the bases if appropriate. if (ASTNode base = derivedNode.getAs()) { for (base = base.getBase(); base; base = base.getBase()) { auto it = NodeInfos.find(base); // Ignore intermediate nodes that don't add interesting properties. if (it == NodeInfos.end()) continue; auto &baseInfo = it->second; visit(base, baseInfo); } } } template void emitNodeReaderClass() { auto info = ReaderWriterInfo::forReader(); emitNodeReaderWriterClass(info); } template void emitNodeWriterClass() { auto info = ReaderWriterInfo::forWriter(); emitNodeReaderWriterClass(info); } template void emitNodeReaderWriterClass(const ReaderWriterInfo &info); template void emitNodeReaderWriterMethod(NodeClass node, const ReaderWriterInfo &info); void emitPropertiedReaderWriterBody(HasProperties node, const ReaderWriterInfo &info); void emitReadOfProperty(StringRef readerName, Property property); void emitReadOfProperty(StringRef readerName, StringRef name, PropertyType type, StringRef condition = ""); void emitWriteOfProperty(StringRef writerName, Property property); void emitWriteOfProperty(StringRef writerName, StringRef name, PropertyType type, StringRef readCode, StringRef condition = ""); void emitBasicReaderWriterFile(const ReaderWriterInfo &info); void emitDispatcherTemplate(const ReaderWriterInfo &info); void emitPackUnpackOptionalTemplate(const ReaderWriterInfo &info); void emitBasicReaderWriterTemplate(const ReaderWriterInfo &info); void emitCasedReaderWriterMethodBody(PropertyType type, const CasedTypeInfo &typeCases, const ReaderWriterInfo &info); private: class Validator { ASTPropsEmitter &Emitter; std::set ValidatedNodes; public: Validator(ASTPropsEmitter &emitter) : Emitter(emitter) {} void validate(); private: void validateNode(HasProperties node, const NodeInfo &nodeInfo); void validateType(PropertyType type, WrappedRecord context); }; }; } // end anonymous namespace void ASTPropsEmitter::Validator::validate() { for (auto &entry : Emitter.NodeInfos) { validateNode(entry.first, entry.second); } if (ErrorsPrinted > 0) { PrintFatalError("property validation failed"); } } void ASTPropsEmitter::Validator::validateNode(HasProperties derivedNode, const NodeInfo &derivedNodeInfo) { if (!ValidatedNodes.insert(derivedNode).second) return; // A map from property name to property. std::map allProperties; Emitter.visitAllNodesWithInfo(derivedNode, derivedNodeInfo, [&](HasProperties node, const NodeInfo &nodeInfo) { for (Property property : nodeInfo.Properties) { validateType(property.getType(), property); auto result = allProperties.insert( std::make_pair(property.getName(), property)); // Diagnose non-unique properties. if (!result.second) { // The existing property is more likely to be associated with a // derived node, so use it as the error. Property existingProperty = result.first->second; PrintError(existingProperty.getLoc(), "multiple properties named \"" + property.getName() + "\" in hierarchy of " + derivedNode.getName()); PrintNote(property.getLoc(), "existing property"); } } }); } void ASTPropsEmitter::Validator::validateType(PropertyType type, WrappedRecord context) { if (!type.isGenericSpecialization()) { if (type.getCXXTypeName() == "") { PrintError(type.getLoc(), "type is not generic but has no C++ type name"); if (context) PrintNote(context.getLoc(), "type used here"); } } else if (auto eltType = type.getArrayElementType()) { validateType(eltType, context); } else if (auto valueType = type.getOptionalElementType()) { validateType(valueType, context); if (valueType.getPackOptionalCode().empty()) { PrintError(valueType.getLoc(), "type doesn't provide optional-packing code"); if (context) PrintNote(context.getLoc(), "type used here"); } else if (valueType.getUnpackOptionalCode().empty()) { PrintError(valueType.getLoc(), "type doesn't provide optional-unpacking code"); if (context) PrintNote(context.getLoc(), "type used here"); } } else { PrintError(type.getLoc(), "unknown generic property type"); if (context) PrintNote(context.getLoc(), "type used here"); } } /****************************************************************************/ /**************************** AST READER/WRITERS ****************************/ /****************************************************************************/ template void ASTPropsEmitter::emitNodeReaderWriterClass(const ReaderWriterInfo &info) { StringRef suffix = info.ClassSuffix; StringRef var = info.HelperVariable; // Enter the class declaration. Out << "template \n" "class Abstract" << info.HierarchyName << suffix << " {\n" "public:\n" " Property" << suffix << " &" << var << ";\n\n"; // Emit the constructor. Out << " Abstract" << info.HierarchyName << suffix << "(Property" << suffix << " &" << var << ") : " << var << "(" << var << ") {}\n\n"; // Emit a method that dispatches on a kind to the appropriate node-specific // method. Out << " " << info.ResultType << " " << info.MethodPrefix << "("; if (info.IsReader) Out << NodeClass::getASTIdTypeName() << " kind"; else Out << "const " << info.HierarchyName << " *node"; Out << ") {\n" " switch ("; if (info.IsReader) Out << "kind"; else Out << "node->" << NodeClass::getASTIdAccessorName() << "()"; Out << ") {\n"; visitASTNodeHierarchy(Records, [&](NodeClass node, NodeClass _) { if (node.isAbstract()) return; Out << " case " << info.HierarchyName << "::" << node.getId() << ":\n" " return " << info.MethodPrefix << node.getClassName() << "("; if (!info.IsReader) Out << "static_cast(node)"; Out << ");\n"; }); Out << " }\n" " llvm_unreachable(\"bad kind\");\n" " }\n\n"; // Emit node-specific methods for all the concrete nodes. visitASTNodeHierarchy(Records, [&](NodeClass node, NodeClass base) { if (node.isAbstract()) return; emitNodeReaderWriterMethod(node, info); }); // Finish the class. Out << "};\n\n"; } /// Emit a reader method for the given concrete AST node class. template void ASTPropsEmitter::emitNodeReaderWriterMethod(NodeClass node, const ReaderWriterInfo &info) { // Declare and start the method. Out << " " << info.ResultType << " " << info.MethodPrefix << node.getClassName() << "("; if (!info.IsReader) Out << "const " << node.getClassName() << " *node"; Out << ") {\n"; if (info.IsReader) Out << " auto &ctx = " << info.HelperVariable << ".getASTContext();\n"; emitPropertiedReaderWriterBody(node, info); // Finish the method declaration. Out << " }\n\n"; } void ASTPropsEmitter::emitPropertiedReaderWriterBody(HasProperties node, const ReaderWriterInfo &info) { // Find the information for this node. auto it = NodeInfos.find(node); if (it == NodeInfos.end()) PrintFatalError(node.getLoc(), "no information about how to deserialize \"" + node.getName() + "\""); auto &nodeInfo = it->second; StringRef creationCode; if (info.IsReader) { // We should have a creation rule. if (!nodeInfo.Creator) PrintFatalError(node.getLoc(), "no " CreationRuleClassName " for \"" + node.getName() + "\""); creationCode = nodeInfo.Creator.getCreationCode(); } // Emit the ReadHelper code, if present. if (!info.IsReader && nodeInfo.ReadHelper) { Out << " " << nodeInfo.ReadHelper.getHelperCode() << "\n"; } // Emit code to read all the properties. visitAllProperties(node, nodeInfo, [&](Property prop) { // Verify that the creation code refers to this property. if (info.IsReader && creationCode.find(prop.getName()) == StringRef::npos) PrintFatalError(nodeInfo.Creator.getLoc(), "creation code for " + node.getName() + " doesn't refer to property \"" + prop.getName() + "\""); // Emit code to read or write this property. if (info.IsReader) emitReadOfProperty(info.HelperVariable, prop); else emitWriteOfProperty(info.HelperVariable, prop); }); // Emit the final creation code. if (info.IsReader) Out << " " << creationCode << "\n"; } static void emitBasicReaderWriterMethodSuffix(raw_ostream &out, PropertyType type, bool isForRead) { if (!type.isGenericSpecialization()) { out << type.getAbstractTypeName(); } else if (auto eltType = type.getArrayElementType()) { out << "Array"; // We only include an explicit template argument for reads so that // we don't cause spurious const mismatches. if (isForRead) { out << "<"; eltType.emitCXXValueTypeName(isForRead, out); out << ">"; } } else if (auto valueType = type.getOptionalElementType()) { out << "Optional"; // We only include an explicit template argument for reads so that // we don't cause spurious const mismatches. if (isForRead) { out << "<"; valueType.emitCXXValueTypeName(isForRead, out); out << ">"; } } else { PrintFatalError(type.getLoc(), "unexpected generic property type"); } } /// Emit code to read the given property in a node-reader method. void ASTPropsEmitter::emitReadOfProperty(StringRef readerName, Property property) { emitReadOfProperty(readerName, property.getName(), property.getType(), property.getCondition()); } void ASTPropsEmitter::emitReadOfProperty(StringRef readerName, StringRef name, PropertyType type, StringRef condition) { // Declare all the necessary buffers. auto bufferTypes = type.getBufferElementTypes(); for (size_t i = 0, e = bufferTypes.size(); i != e; ++i) { Out << " llvm::SmallVector<"; PropertyType(bufferTypes[i]).emitCXXValueTypeName(/*for read*/ true, Out); Out << ", 8> " << name << "_buffer_" << i << ";\n"; } // T prop = R.find("prop").read##ValueType(buffers...); // We intentionally ignore shouldPassByReference here: we're going to // get a pr-value back from read(), and we should be able to forward // that in the creation rule. Out << " "; if (!condition.empty()) Out << "llvm::Optional<"; type.emitCXXValueTypeName(true, Out); if (!condition.empty()) Out << ">"; Out << " " << name; if (condition.empty()) { Out << " = "; } else { Out << ";\n" " if (" << condition << ") {\n" " " << name << ".emplace("; } Out << readerName << ".find(\"" << name << "\")." << (type.isGenericSpecialization() ? "template " : "") << "read"; emitBasicReaderWriterMethodSuffix(Out, type, /*for read*/ true); Out << "("; for (size_t i = 0, e = bufferTypes.size(); i != e; ++i) { Out << (i > 0 ? ", " : "") << name << "_buffer_" << i; } Out << ")"; if (condition.empty()) { Out << ";\n"; } else { Out << ");\n" " }\n"; } } /// Emit code to write the given property in a node-writer method. void ASTPropsEmitter::emitWriteOfProperty(StringRef writerName, Property property) { emitWriteOfProperty(writerName, property.getName(), property.getType(), property.getReadCode(), property.getCondition()); } void ASTPropsEmitter::emitWriteOfProperty(StringRef writerName, StringRef name, PropertyType type, StringRef readCode, StringRef condition) { if (!condition.empty()) { Out << " if (" << condition << ") {\n"; } // Focus down to the property: // T prop = ; // W.find("prop").write##ValueType(prop); Out << " "; type.emitCXXValueTypeName(false, Out); Out << " " << name << " = (" << readCode << ");\n" " " << writerName << ".find(\"" << name << "\").write"; emitBasicReaderWriterMethodSuffix(Out, type, /*for read*/ false); Out << "(" << name << ");\n"; if (!condition.empty()) { Out << " }\n"; } } /// Emit an .inc file that defines the AbstractFooReader class /// for the given AST class hierarchy. template static void emitASTReader(RecordKeeper &records, raw_ostream &out, StringRef description) { emitSourceFileHeader(description, out); ASTPropsEmitter(records, out).emitNodeReaderClass(); } void clang::EmitClangTypeReader(RecordKeeper &records, raw_ostream &out) { emitASTReader(records, out, "A CRTP reader for Clang Type nodes"); } /// Emit an .inc file that defines the AbstractFooWriter class /// for the given AST class hierarchy. template static void emitASTWriter(RecordKeeper &records, raw_ostream &out, StringRef description) { emitSourceFileHeader(description, out); ASTPropsEmitter(records, out).emitNodeWriterClass(); } void clang::EmitClangTypeWriter(RecordKeeper &records, raw_ostream &out) { emitASTWriter(records, out, "A CRTP writer for Clang Type nodes"); } /****************************************************************************/ /*************************** BASIC READER/WRITERS ***************************/ /****************************************************************************/ void ASTPropsEmitter::emitDispatcherTemplate(const ReaderWriterInfo &info) { // Declare the {Read,Write}Dispatcher template. StringRef dispatcherPrefix = (info.IsReader ? "Read" : "Write"); Out << "template \n" "struct " << dispatcherPrefix << "Dispatcher;\n"; // Declare a specific specialization of the dispatcher template. auto declareSpecialization = [&](StringRef specializationParameters, const Twine &cxxTypeName, StringRef methodSuffix) { StringRef var = info.HelperVariable; Out << "template " << specializationParameters << "\n" "struct " << dispatcherPrefix << "Dispatcher<" << cxxTypeName << "> {\n"; Out << " template \n" " static " << (info.IsReader ? cxxTypeName : "void") << " " << info.MethodPrefix << "(Basic" << info.ClassSuffix << " &" << var << ", Args &&... args) {\n" " return " << var << "." << info.MethodPrefix << methodSuffix << "(std::forward(args)...);\n" " }\n" "};\n"; }; // Declare explicit specializations for each of the concrete types. for (PropertyType type : AllPropertyTypes) { declareSpecialization("<>", type.getCXXTypeName(), type.getAbstractTypeName()); // Also declare a specialization for the const type when appropriate. if (!info.IsReader && type.isConstWhenWriting()) { declareSpecialization("<>", "const " + type.getCXXTypeName(), type.getAbstractTypeName()); } } // Declare partial specializations for ArrayRef and Optional. declareSpecialization("", "llvm::ArrayRef", "Array"); declareSpecialization("", "llvm::Optional", "Optional"); Out << "\n"; } void ASTPropsEmitter::emitPackUnpackOptionalTemplate(const ReaderWriterInfo &info) { StringRef classPrefix = (info.IsReader ? "Unpack" : "Pack"); StringRef methodName = (info.IsReader ? "unpack" : "pack"); // Declare the {Pack,Unpack}OptionalValue template. Out << "template \n" "struct " << classPrefix << "OptionalValue;\n"; auto declareSpecialization = [&](const Twine &typeName, StringRef code) { Out << "template <>\n" "struct " << classPrefix << "OptionalValue<" << typeName << "> {\n" " static " << (info.IsReader ? "Optional<" : "") << typeName << (info.IsReader ? "> " : " ") << methodName << "(" << (info.IsReader ? "" : "Optional<") << typeName << (info.IsReader ? "" : ">") << " value) {\n" " return " << code << ";\n" " }\n" "};\n"; }; for (PropertyType type : AllPropertyTypes) { StringRef code = (info.IsReader ? type.getUnpackOptionalCode() : type.getPackOptionalCode()); if (code.empty()) continue; StringRef typeName = type.getCXXTypeName(); declareSpecialization(typeName, code); if (type.isConstWhenWriting() && !info.IsReader) declareSpecialization("const " + typeName, code); } Out << "\n"; } void ASTPropsEmitter::emitBasicReaderWriterTemplate(const ReaderWriterInfo &info) { // Emit the Basic{Reader,Writer}Base template. Out << "template \n" "class Basic" << info.ClassSuffix << "Base {\n"; Out << " ASTContext &C;\n"; Out << "protected:\n" " Basic" << info.ClassSuffix << "Base" << ("(ASTContext &ctx) : C(ctx)") << " {}\n" "public:\n"; Out << " ASTContext &getASTContext() { return C; }\n"; Out << " Impl &asImpl() { return static_cast(*this); }\n"; auto enterReaderWriterMethod = [&](StringRef cxxTypeName, StringRef abstractTypeName, bool shouldPassByReference, bool constWhenWriting, StringRef paramName) { Out << " " << (info.IsReader ? cxxTypeName : "void") << " " << info.MethodPrefix << abstractTypeName << "("; if (!info.IsReader) Out << (shouldPassByReference || constWhenWriting ? "const " : "") << cxxTypeName << (shouldPassByReference ? " &" : "") << " " << paramName; Out << ") {\n"; }; // Emit {read,write}ValueType methods for all the enum and subclass types // that default to using the integer/base-class implementations. for (PropertyType type : AllPropertyTypes) { auto enterMethod = [&](StringRef paramName) { enterReaderWriterMethod(type.getCXXTypeName(), type.getAbstractTypeName(), type.shouldPassByReference(), type.isConstWhenWriting(), paramName); }; auto exitMethod = [&] { Out << " }\n"; }; // Handled cased types. auto casedIter = CasedTypeInfos.find(type); if (casedIter != CasedTypeInfos.end()) { enterMethod("node"); emitCasedReaderWriterMethodBody(type, casedIter->second, info); exitMethod(); } else if (type.isEnum()) { enterMethod("value"); if (info.IsReader) Out << " return asImpl().template readEnum<" << type.getCXXTypeName() << ">();\n"; else Out << " asImpl().writeEnum(value);\n"; exitMethod(); } else if (PropertyType superclass = type.getSuperclassType()) { enterMethod("value"); if (info.IsReader) Out << " return cast_or_null<" << type.getSubclassClassName() << ">(asImpl().read" << superclass.getAbstractTypeName() << "());\n"; else Out << " asImpl().write" << superclass.getAbstractTypeName() << "(value);\n"; exitMethod(); } else { // The other types can't be handled as trivially. } } Out << "};\n\n"; } void ASTPropsEmitter::emitCasedReaderWriterMethodBody(PropertyType type, const CasedTypeInfo &typeCases, const ReaderWriterInfo &info) { if (typeCases.Cases.empty()) { assert(typeCases.KindRule); PrintFatalError(typeCases.KindRule.getLoc(), "no cases found for \"" + type.getCXXTypeName() + "\""); } if (!typeCases.KindRule) { assert(!typeCases.Cases.empty()); PrintFatalError(typeCases.Cases.front().getLoc(), "no kind rule for \"" + type.getCXXTypeName() + "\""); } auto var = info.HelperVariable; std::string subvar = ("sub" + var).str(); // Bind `ctx` for readers. if (info.IsReader) Out << " auto &ctx = asImpl().getASTContext();\n"; // Start an object. Out << " auto &&" << subvar << " = asImpl()." << info.MethodPrefix << "Object();\n"; // Read/write the kind property; TypeKindRule kindRule = typeCases.KindRule; StringRef kindProperty = kindRule.getKindPropertyName(); PropertyType kindType = kindRule.getKindType(); if (info.IsReader) { emitReadOfProperty(subvar, kindProperty, kindType); } else { // Write the property. Note that this will implicitly read the // kind into a local variable with the right name. emitWriteOfProperty(subvar, kindProperty, kindType, kindRule.getReadCode()); } // Prepare a ReaderWriterInfo with a helper variable that will use // the sub-reader/writer. ReaderWriterInfo subInfo = info; subInfo.HelperVariable = subvar; // Switch on the kind. Out << " switch (" << kindProperty << ") {\n"; for (TypeCase typeCase : typeCases.Cases) { Out << " case " << type.getCXXTypeName() << "::" << typeCase.getCaseName() << ": {\n"; emitPropertiedReaderWriterBody(typeCase, subInfo); if (!info.IsReader) Out << " return;\n"; Out << " }\n\n"; } Out << " }\n" " llvm_unreachable(\"bad " << kindType.getCXXTypeName() << "\");\n"; } void ASTPropsEmitter::emitBasicReaderWriterFile(const ReaderWriterInfo &info) { emitDispatcherTemplate(info); emitPackUnpackOptionalTemplate(info); emitBasicReaderWriterTemplate(info); } /// Emit an .inc file that defines some helper classes for reading /// basic values. void clang::EmitClangBasicReader(RecordKeeper &records, raw_ostream &out) { emitSourceFileHeader("Helper classes for BasicReaders", out); // Use any property, we won't be using those properties. auto info = ReaderWriterInfo::forReader(); ASTPropsEmitter(records, out).emitBasicReaderWriterFile(info); } /// Emit an .inc file that defines some helper classes for writing /// basic values. void clang::EmitClangBasicWriter(RecordKeeper &records, raw_ostream &out) { emitSourceFileHeader("Helper classes for BasicWriters", out); // Use any property, we won't be using those properties. auto info = ReaderWriterInfo::forWriter(); ASTPropsEmitter(records, out).emitBasicReaderWriterFile(info); }