//===--- NodePrinter.cpp - Swift Demangling Node Printer ------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // This file implements the node printer for demangle node trees. // //===----------------------------------------------------------------------===// #include "swift/Demangling/Demangle.h" #include "swift/Strings.h" #include #include using namespace swift; using namespace Demangle; using llvm::StringRef; [[noreturn]] static void printer_unreachable(const char *Message) { fprintf(stderr, "fatal error: %s\n", Message); std::abort(); } DemanglerPrinter &DemanglerPrinter::operator<<(unsigned long long n) & { char buffer[32]; snprintf(buffer, sizeof(buffer), "%llu", n); Stream.append(buffer); return *this; } DemanglerPrinter &DemanglerPrinter::operator<<(long long n) & { char buffer[32]; snprintf(buffer, sizeof(buffer), "%lld",n); Stream.append(buffer); return *this; } std::string Demangle::archetypeName(Node::IndexType index, Node::IndexType depth) { DemanglerPrinter name; do { name << (char)('A' + (index % 26)); index /= 26; } while (index); if (depth != 0) name << depth; return std::move(name).str(); } namespace { struct QuotedString { std::string Value; explicit QuotedString(std::string Value) : Value(Value) {} }; static DemanglerPrinter &operator<<(DemanglerPrinter &printer, const QuotedString &QS) { printer << '"'; for (auto C : QS.Value) { switch (C) { case '\\': printer << "\\\\"; break; case '\t': printer << "\\t"; break; case '\n': printer << "\\n"; break; case '\r': printer << "\\r"; break; case '"': printer << "\\\""; break; case '\'': printer << '\''; break; // no need to escape these case '\0': printer << "\\0"; break; default: auto c = static_cast(C); // Other ASCII control characters should get escaped. if (c < 0x20 || c == 0x7F) { static const char Hexdigit[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; printer << "\\x" << Hexdigit[c >> 4] << Hexdigit[c & 0xF]; } else { printer << c; } break; } } printer << '"'; return printer; } static StringRef toString(Directness d) { switch (d) { case Directness::Direct: return "direct"; case Directness::Indirect: return "indirect"; } printer_unreachable("bad directness"); } static StringRef toString(ValueWitnessKind k) { switch (k) { case ValueWitnessKind::AllocateBuffer: return "allocateBuffer"; case ValueWitnessKind::AssignWithCopy: return "assignWithCopy"; case ValueWitnessKind::AssignWithTake: return "assignWithTake"; case ValueWitnessKind::DeallocateBuffer: return "deallocateBuffer"; case ValueWitnessKind::Destroy: return "destroy"; case ValueWitnessKind::DestroyBuffer: return "destroyBuffer"; case ValueWitnessKind::InitializeBufferWithCopyOfBuffer: return "initializeBufferWithCopyOfBuffer"; case ValueWitnessKind::InitializeBufferWithCopy: return "initializeBufferWithCopy"; case ValueWitnessKind::InitializeWithCopy: return "initializeWithCopy"; case ValueWitnessKind::InitializeBufferWithTake: return "initializeBufferWithTake"; case ValueWitnessKind::InitializeWithTake: return "initializeWithTake"; case ValueWitnessKind::ProjectBuffer: return "projectBuffer"; case ValueWitnessKind::InitializeBufferWithTakeOfBuffer: return "initializeBufferWithTakeOfBuffer"; case ValueWitnessKind::DestroyArray: return "destroyArray"; case ValueWitnessKind::InitializeArrayWithCopy: return "initializeArrayWithCopy"; case ValueWitnessKind::InitializeArrayWithTakeFrontToBack: return "initializeArrayWithTakeFrontToBack"; case ValueWitnessKind::InitializeArrayWithTakeBackToFront: return "initializeArrayWithTakeBackToFront"; case ValueWitnessKind::StoreExtraInhabitant: return "storeExtraInhabitant"; case ValueWitnessKind::GetExtraInhabitantIndex: return "getExtraInhabitantIndex"; case ValueWitnessKind::GetEnumTag: return "getEnumTag"; case ValueWitnessKind::DestructiveProjectEnumData: return "destructiveProjectEnumData"; case ValueWitnessKind::DestructiveInjectEnumTag: return "destructiveInjectEnumTag"; } printer_unreachable("bad value witness kind"); } class NodePrinter { private: DemanglerPrinter Printer; DemangleOptions Options; public: NodePrinter(DemangleOptions options) : Options(options) {} std::string printRoot(NodePointer root) { print(root); return std::move(Printer).str(); } private: void printChildren(Node::iterator begin, Node::iterator end, const char *sep = nullptr) { for (; begin != end;) { print(*begin); ++begin; if (sep && begin != end) Printer << sep; } } void printChildren(NodePointer pointer, const char *sep = nullptr) { if (!pointer) return; Node::iterator begin = pointer->begin(), end = pointer->end(); printChildren(begin, end, sep); } NodePointer getFirstChildOfKind(NodePointer pointer, Node::Kind kind) { if (!pointer) return nullptr; for (NodePointer &child : *pointer) { if (child && child->getKind() == kind) return child; } return nullptr; } void printBoundGenericNoSugar(NodePointer pointer) { if (pointer->getNumChildren() < 2) return; NodePointer typelist = pointer->getChild(1); print(pointer->getChild(0)); Printer << "<"; printChildren(typelist, ", "); Printer << ">"; } static bool isSwiftModule(NodePointer node) { return (node->getKind() == Node::Kind::Module && node->getText() == STDLIB_NAME); } static bool isDebuggerGeneratedModule(NodePointer node) { return (node->getKind() == Node::Kind::Module && node->getText().startswith(LLDB_EXPRESSIONS_MODULE_NAME_PREFIX)); } static bool isIdentifier(NodePointer node, StringRef desired) { return (node->getKind() == Node::Kind::Identifier && node->getText() == desired); } enum class SugarType { None, Optional, ImplicitlyUnwrappedOptional, Array, Dictionary }; /// Determine whether this is a "simple" type, from the type-simple /// production. bool isSimpleType(NodePointer pointer) { switch (pointer->getKind()) { case Node::Kind::AssociatedType: case Node::Kind::AssociatedTypeRef: case Node::Kind::BoundGenericClass: case Node::Kind::BoundGenericEnum: case Node::Kind::BoundGenericStructure: case Node::Kind::BuiltinTypeName: case Node::Kind::Class: case Node::Kind::DependentGenericType: case Node::Kind::DependentMemberType: case Node::Kind::DependentGenericParamType: case Node::Kind::DynamicSelf: case Node::Kind::Enum: case Node::Kind::ErrorType: case Node::Kind::ExistentialMetatype: case Node::Kind::Metatype: case Node::Kind::MetatypeRepresentation: case Node::Kind::Module: case Node::Kind::Tuple: case Node::Kind::Protocol: case Node::Kind::QualifiedArchetype: case Node::Kind::ReturnType: case Node::Kind::SILBoxType: case Node::Kind::SILBoxTypeWithLayout: case Node::Kind::Structure: case Node::Kind::TupleElementName: case Node::Kind::Type: case Node::Kind::TypeAlias: case Node::Kind::TypeList: return true; case Node::Kind::ProtocolList: if (pointer->getChild(0)->getNumChildren() <= 1) return true; return false; case Node::Kind::Allocator: case Node::Kind::ArgumentTuple: case Node::Kind::AssociatedTypeMetadataAccessor: case Node::Kind::AssociatedTypeWitnessTableAccessor: case Node::Kind::AutoClosureType: case Node::Kind::CFunctionPointer: case Node::Kind::Constructor: case Node::Kind::CurryThunk: case Node::Kind::Deallocator: case Node::Kind::DeclContext: case Node::Kind::DefaultArgumentInitializer: case Node::Kind::DependentAssociatedTypeRef: case Node::Kind::DependentGenericSignature: case Node::Kind::DependentGenericParamCount: case Node::Kind::DependentGenericConformanceRequirement: case Node::Kind::DependentGenericLayoutRequirement: case Node::Kind::DependentGenericSameTypeRequirement: case Node::Kind::DependentPseudogenericSignature: case Node::Kind::Destructor: case Node::Kind::DidSet: case Node::Kind::DirectMethodReferenceAttribute: case Node::Kind::Directness: case Node::Kind::DynamicAttribute: case Node::Kind::ExplicitClosure: case Node::Kind::Extension: case Node::Kind::FieldOffset: case Node::Kind::FullTypeMetadata: case Node::Kind::Function: case Node::Kind::FunctionSignatureSpecialization: case Node::Kind::FunctionSignatureSpecializationParam: case Node::Kind::FunctionSignatureSpecializationParamKind: case Node::Kind::FunctionSignatureSpecializationParamPayload: case Node::Kind::FunctionType: case Node::Kind::GenericProtocolWitnessTable: case Node::Kind::GenericProtocolWitnessTableInstantiationFunction: case Node::Kind::GenericPartialSpecialization: case Node::Kind::GenericPartialSpecializationNotReAbstracted: case Node::Kind::GenericSpecialization: case Node::Kind::GenericSpecializationNotReAbstracted: case Node::Kind::GenericSpecializationParam: case Node::Kind::GenericTypeMetadataPattern: case Node::Kind::Getter: case Node::Kind::Global: case Node::Kind::GlobalGetter: case Node::Kind::Identifier: case Node::Kind::Index: case Node::Kind::IVarInitializer: case Node::Kind::IVarDestroyer: case Node::Kind::ImplConvention: case Node::Kind::ImplFunctionAttribute: case Node::Kind::ImplFunctionType: case Node::Kind::ImplicitClosure: case Node::Kind::ImplParameter: case Node::Kind::ImplResult: case Node::Kind::ImplErrorResult: case Node::Kind::InOut: case Node::Kind::InfixOperator: case Node::Kind::Initializer: case Node::Kind::LazyProtocolWitnessTableAccessor: case Node::Kind::LazyProtocolWitnessTableCacheVariable: case Node::Kind::LocalDeclName: case Node::Kind::PrivateDeclName: case Node::Kind::MaterializeForSet: case Node::Kind::Metaclass: case Node::Kind::NativeOwningAddressor: case Node::Kind::NativeOwningMutableAddressor: case Node::Kind::NativePinningAddressor: case Node::Kind::NativePinningMutableAddressor: case Node::Kind::NominalTypeDescriptor: case Node::Kind::NonObjCAttribute: case Node::Kind::Number: case Node::Kind::ObjCAttribute: case Node::Kind::ObjCBlock: case Node::Kind::OwningAddressor: case Node::Kind::OwningMutableAddressor: case Node::Kind::PartialApplyForwarder: case Node::Kind::PartialApplyObjCForwarder: case Node::Kind::PostfixOperator: case Node::Kind::PrefixOperator: case Node::Kind::ProtocolConformance: case Node::Kind::ProtocolDescriptor: case Node::Kind::ProtocolWitness: case Node::Kind::ProtocolWitnessTable: case Node::Kind::ProtocolWitnessTableAccessor: case Node::Kind::ReabstractionThunk: case Node::Kind::ReabstractionThunkHelper: case Node::Kind::Setter: case Node::Kind::SILBoxLayout: case Node::Kind::SILBoxMutableField: case Node::Kind::SILBoxImmutableField: case Node::Kind::SpecializationIsFragile: case Node::Kind::SpecializationPassID: case Node::Kind::Static: case Node::Kind::Subscript: case Node::Kind::Suffix: case Node::Kind::ThinFunctionType: case Node::Kind::TupleElement: case Node::Kind::TypeMangling: case Node::Kind::TypeMetadata: case Node::Kind::TypeMetadataAccessFunction: case Node::Kind::TypeMetadataLazyCache: case Node::Kind::UncurriedFunctionType: case Node::Kind::Unmanaged: case Node::Kind::Unowned: case Node::Kind::UnsafeAddressor: case Node::Kind::UnsafeMutableAddressor: case Node::Kind::ValueWitness: case Node::Kind::ValueWitnessTable: case Node::Kind::Variable: case Node::Kind::VTableAttribute: case Node::Kind::VTableThunk: case Node::Kind::Weak: case Node::Kind::WillSet: case Node::Kind::WitnessTableOffset: case Node::Kind::ReflectionMetadataBuiltinDescriptor: case Node::Kind::ReflectionMetadataFieldDescriptor: case Node::Kind::ReflectionMetadataAssocTypeDescriptor: case Node::Kind::ReflectionMetadataSuperclassDescriptor: case Node::Kind::GenericTypeParamDecl: case Node::Kind::ThrowsAnnotation: case Node::Kind::EmptyList: case Node::Kind::FirstElementMarker: case Node::Kind::VariadicMarker: case Node::Kind::OutlinedCopy: case Node::Kind::OutlinedConsume: return false; } printer_unreachable("bad node kind"); } SugarType findSugar(NodePointer pointer) { if (pointer->getNumChildren() == 1 && pointer->getKind() == Node::Kind::Type) return findSugar(pointer->getChild(0)); if (pointer->getNumChildren() != 2) return SugarType::None; if (pointer->getKind() != Node::Kind::BoundGenericEnum && pointer->getKind() != Node::Kind::BoundGenericStructure) return SugarType::None; auto unboundType = pointer->getChild(0)->getChild(0); // drill through Type auto typeArgs = pointer->getChild(1); if (pointer->getKind() == Node::Kind::BoundGenericEnum) { // Swift.Optional if (isIdentifier(unboundType->getChild(1), "Optional") && typeArgs->getNumChildren() == 1 && isSwiftModule(unboundType->getChild(0))) { return SugarType::Optional; } // Swift.ImplicitlyUnwrappedOptional if (isIdentifier(unboundType->getChild(1), "ImplicitlyUnwrappedOptional") && typeArgs->getNumChildren() == 1 && isSwiftModule(unboundType->getChild(0))) { return SugarType::ImplicitlyUnwrappedOptional; } return SugarType::None; } assert(pointer->getKind() == Node::Kind::BoundGenericStructure); // Array if (isIdentifier(unboundType->getChild(1), "Array") && typeArgs->getNumChildren() == 1 && isSwiftModule(unboundType->getChild(0))) { return SugarType::Array; } // Dictionary if (isIdentifier(unboundType->getChild(1), "Dictionary") && typeArgs->getNumChildren() == 2 && isSwiftModule(unboundType->getChild(0))) { return SugarType::Dictionary; } return SugarType::None; } void printBoundGeneric(NodePointer pointer) { if (pointer->getNumChildren() < 2) return; if (pointer->getNumChildren() != 2) { printBoundGenericNoSugar(pointer); return; } if (!Options.SynthesizeSugarOnTypes || pointer->getKind() == Node::Kind::BoundGenericClass) { // no sugar here printBoundGenericNoSugar(pointer); return; } SugarType sugarType = findSugar(pointer); switch (sugarType) { case SugarType::None: printBoundGenericNoSugar(pointer); break; case SugarType::Optional: case SugarType::ImplicitlyUnwrappedOptional: { NodePointer type = pointer->getChild(1)->getChild(0); bool needs_parens = !isSimpleType(type); if (needs_parens) Printer << "("; print(type); if (needs_parens) Printer << ")"; Printer << (sugarType == SugarType::Optional ? "?" : "!"); break; } case SugarType::Array: { NodePointer type = pointer->getChild(1)->getChild(0); Printer << "["; print(type); Printer << "]"; break; } case SugarType::Dictionary: { NodePointer keyType = pointer->getChild(1)->getChild(0); NodePointer valueType = pointer->getChild(1)->getChild(1); Printer << "["; print(keyType); Printer << " : "; print(valueType); Printer << "]"; break; } } } void printSimplifiedEntityType(NodePointer context, NodePointer entityType); void printFunctionType(NodePointer node) { assert(node->getNumChildren() == 2 || node->getNumChildren() == 3); unsigned startIndex = 0; bool throws = false; if (node->getNumChildren() == 3) { assert(node->getChild(0)->getKind() == Node::Kind::ThrowsAnnotation); startIndex++; throws = true; } print(node->getChild(startIndex)); if (throws) Printer << " throws"; print(node->getChild(startIndex+1)); } void printImplFunctionType(NodePointer fn) { enum State { Attrs, Inputs, Results } curState = Attrs; auto transitionTo = [&](State newState) { assert(newState >= curState); for (; curState != newState; curState = State(curState + 1)) { switch (curState) { case Attrs: Printer << '('; continue; case Inputs: Printer << ") -> ("; continue; case Results: printer_unreachable("no state after Results"); } printer_unreachable("bad state"); } }; for (auto &child : *fn) { if (child->getKind() == Node::Kind::ImplParameter) { if (curState == Inputs) Printer << ", "; transitionTo(Inputs); print(child); } else if (child->getKind() == Node::Kind::ImplResult || child->getKind() == Node::Kind::ImplErrorResult) { if (curState == Results) Printer << ", "; transitionTo(Results); print(child); } else { assert(curState == Attrs); print(child); Printer << ' '; } } transitionTo(Results); Printer << ')'; } void printContext(NodePointer context) { // TODO: parenthesize local contexts? if (Options.DisplayDebuggerGeneratedModule || !isDebuggerGeneratedModule(context)) { print(context, /*asContext*/ true); if (context->getKind() == Node::Kind::Module && !Options.DisplayModuleNames) return; Printer << '.'; } } void print(NodePointer pointer, bool asContext = false, bool suppressType = false); unsigned printFunctionSigSpecializationParam(NodePointer pointer, unsigned Idx); void printSpecializationPrefix(NodePointer node, StringRef Description, StringRef ParamPrefix = StringRef()); }; } // end anonymous namespace static bool isExistentialType(NodePointer node) { return (node->getKind() == Node::Kind::ExistentialMetatype || node->getKind() == Node::Kind::ProtocolList); } /// Print the relevant parameters and return the new index. unsigned NodePrinter::printFunctionSigSpecializationParam(NodePointer pointer, unsigned Idx) { NodePointer firstChild = pointer->getChild(Idx); unsigned V = firstChild->getIndex(); auto K = FunctionSigSpecializationParamKind(V); switch (K) { case FunctionSigSpecializationParamKind::BoxToValue: case FunctionSigSpecializationParamKind::BoxToStack: print(pointer->getChild(Idx++)); return Idx; case FunctionSigSpecializationParamKind::ConstantPropFunction: case FunctionSigSpecializationParamKind::ConstantPropGlobal: { Printer << "["; print(pointer->getChild(Idx++)); Printer << " : "; const auto &text = pointer->getChild(Idx++)->getText(); std::string demangledName = demangleSymbolAsString(text); if (demangledName.empty()) { Printer << text; } else { Printer << demangledName; } Printer << "]"; return Idx; } case FunctionSigSpecializationParamKind::ConstantPropInteger: case FunctionSigSpecializationParamKind::ConstantPropFloat: Printer << "["; print(pointer->getChild(Idx++)); Printer << " : "; print(pointer->getChild(Idx++)); Printer << "]"; return Idx; case FunctionSigSpecializationParamKind::ConstantPropString: Printer << "["; print(pointer->getChild(Idx++)); Printer << " : "; print(pointer->getChild(Idx++)); Printer << "'"; print(pointer->getChild(Idx++)); Printer << "'"; Printer << "]"; return Idx; case FunctionSigSpecializationParamKind::ClosureProp: Printer << "["; print(pointer->getChild(Idx++)); Printer << " : "; print(pointer->getChild(Idx++)); Printer << ", Argument Types : ["; for (unsigned e = pointer->getNumChildren(); Idx < e;) { NodePointer child = pointer->getChild(Idx); // Until we no longer have a type node, keep demangling. if (child->getKind() != Node::Kind::Type) break; print(child); ++Idx; // If we are not done, print the ", ". if (Idx < e && pointer->getChild(Idx)->hasText()) Printer << ", "; } Printer << "]"; return Idx; default: break; } assert( ((V & unsigned(FunctionSigSpecializationParamKind::OwnedToGuaranteed)) || (V & unsigned(FunctionSigSpecializationParamKind::SROA)) || (V & unsigned(FunctionSigSpecializationParamKind::Dead))) && "Invalid OptionSet"); print(pointer->getChild(Idx++)); return Idx; } void NodePrinter::printSpecializationPrefix(NodePointer node, StringRef Description, StringRef ParamPrefix) { if (!Options.DisplayGenericSpecializations) { Printer << "specialized "; return; } Printer << Description << " <"; const char *Separator = ""; for (unsigned i = 0, e = node->getNumChildren(); i < e; ++i) { switch (node->getChild(i)->getKind()) { case Node::Kind::SpecializationPassID: // We skip the SpecializationPassID since it does not contain any // information that is useful to our users. break; case Node::Kind::SpecializationIsFragile: Printer << Separator; Separator = ", "; print(node->getChild(i)); break; default: // Ignore empty specializations. if (node->getChild(i)->hasChildren()) { Printer << Separator << ParamPrefix; Separator = ", "; print(node->getChild(i)); } break; } } Printer << "> of "; } static bool isClassType(NodePointer pointer) { return pointer->getKind() == Node::Kind::Class; } static bool useColonForEntityType(NodePointer entity, NodePointer type) { switch (entity->getKind()) { case Node::Kind::Variable: case Node::Kind::Initializer: case Node::Kind::DefaultArgumentInitializer: case Node::Kind::IVarInitializer: case Node::Kind::Class: case Node::Kind::Structure: case Node::Kind::Enum: case Node::Kind::Protocol: case Node::Kind::TypeAlias: case Node::Kind::OwningAddressor: case Node::Kind::OwningMutableAddressor: case Node::Kind::NativeOwningAddressor: case Node::Kind::NativeOwningMutableAddressor: case Node::Kind::NativePinningAddressor: case Node::Kind::NativePinningMutableAddressor: case Node::Kind::UnsafeAddressor: case Node::Kind::UnsafeMutableAddressor: case Node::Kind::GlobalGetter: case Node::Kind::Getter: case Node::Kind::Setter: case Node::Kind::MaterializeForSet: case Node::Kind::WillSet: case Node::Kind::DidSet: return true; case Node::Kind::Subscript: case Node::Kind::Function: case Node::Kind::ExplicitClosure: case Node::Kind::ImplicitClosure: case Node::Kind::Allocator: case Node::Kind::Constructor: case Node::Kind::Destructor: case Node::Kind::Deallocator: case Node::Kind::IVarDestroyer: { // We expect to see a function type here, but if we don't, use the colon. type = type->getChild(0); while (type->getKind() == Node::Kind::DependentGenericType) type = type->getChild(1)->getChild(0); return (type->getKind() != Node::Kind::FunctionType && type->getKind() != Node::Kind::UncurriedFunctionType && type->getKind() != Node::Kind::CFunctionPointer && type->getKind() != Node::Kind::ThinFunctionType); } default: printer_unreachable("not an entity"); } } static bool isMethodContext(const NodePointer &context) { switch (context->getKind()) { case Node::Kind::Structure: case Node::Kind::Enum: case Node::Kind::Class: case Node::Kind::Protocol: case Node::Kind::Extension: return true; default: return false; } } /// Perform any desired type simplifications for an entity in Simplified mode. void NodePrinter::printSimplifiedEntityType(NodePointer context, NodePointer entityType) { // Only do anything special to methods. if (!isMethodContext(context)) return print(entityType); // Strip off a single level of uncurried function type. NodePointer type = entityType; assert(type->getKind() == Node::Kind::Type); type = type->getChild(0); if (type->getKind() == Node::Kind::DependentGenericType) { type = type->getChild(1)->getChild(0); } print(entityType); } void NodePrinter::print(NodePointer pointer, bool asContext, bool suppressType) { // Common code for handling entities. auto printEntity = [&](bool hasName, bool hasType, StringRef extraName) { if (Options.QualifyEntities) printContext(pointer->getChild(0)); bool printType = (hasType && !suppressType); bool useParens = (printType && asContext); if (useParens) Printer << '('; if (hasName) print(pointer->getChild(1)); Printer << extraName; if (printType) { NodePointer type = pointer->getChild(1 + unsigned(hasName)); if (useColonForEntityType(pointer, type)) { if (Options.DisplayEntityTypes) { Printer << " : "; print(type); } } else if (!Options.DisplayEntityTypes) { printSimplifiedEntityType(pointer->getChild(0), type); } else { Printer << " "; print(type); } } if (useParens) Printer << ')'; }; Node::Kind kind = pointer->getKind(); switch (kind) { case Node::Kind::Static: Printer << "static "; print(pointer->getChild(0), asContext, suppressType); return; case Node::Kind::CurryThunk: Printer << "curry thunk of "; print(pointer->getChild(0), asContext, suppressType); return; case Node::Kind::OutlinedCopy: Printer << "outlined copy of "; print(pointer->getChild(0), asContext, suppressType); return; case Node::Kind::OutlinedConsume: Printer << "outlined consume of "; print(pointer->getChild(0), asContext, suppressType); return; case Node::Kind::Directness: Printer << toString(Directness(pointer->getIndex())) << " "; return; case Node::Kind::Extension: assert((pointer->getNumChildren() == 2 || pointer->getNumChildren() == 3) && "Extension expects 2 or 3 children."); if (Options.QualifyEntities && Options.DisplayExtensionContexts) { Printer << "(extension in "; // Print the module where extension is defined. print(pointer->getChild(0), true); Printer << "):"; } print(pointer->getChild(1), asContext); if (pointer->getNumChildren() == 3) print(pointer->getChild(2), true); return; case Node::Kind::Variable: case Node::Kind::Function: case Node::Kind::Subscript: case Node::Kind::GenericTypeParamDecl: printEntity(true, true, ""); return; case Node::Kind::ExplicitClosure: case Node::Kind::ImplicitClosure: { auto index = pointer->getChild(1)->getIndex(); DemanglerPrinter printName; printName << '('; if (pointer->getKind() == Node::Kind::ImplicitClosure) printName << "implicit "; printName << "closure #" << (index + 1) << ")"; printEntity(false, false, std::move(printName).str()); return; } case Node::Kind::Global: printChildren(pointer); return; case Node::Kind::Suffix: if (!Options.DisplayUnmangledSuffix) return; Printer << " with unmangled suffix " << QuotedString(pointer->getText()); return; case Node::Kind::Initializer: printEntity(false, false, "(variable initialization expression)"); return; case Node::Kind::DefaultArgumentInitializer: { auto index = pointer->getChild(1); DemanglerPrinter strPrinter; strPrinter << "(default argument " << index->getIndex() << ")"; printEntity(false, false, std::move(strPrinter).str()); return; } case Node::Kind::DeclContext: print(pointer->getChild(0), asContext); return; case Node::Kind::Type: print(pointer->getChild(0), asContext); return; case Node::Kind::TypeMangling: print(pointer->getChild(0)); return; case Node::Kind::Class: case Node::Kind::Structure: case Node::Kind::Enum: case Node::Kind::Protocol: case Node::Kind::TypeAlias: printEntity(true, false, ""); return; case Node::Kind::LocalDeclName: Printer << '('; print(pointer->getChild(1)); Printer << " #" << (pointer->getChild(0)->getIndex() + 1) << ')'; return; case Node::Kind::PrivateDeclName: if (Options.ShowPrivateDiscriminators) Printer << '('; print(pointer->getChild(1)); if (Options.ShowPrivateDiscriminators) Printer << " in " << pointer->getChild(0)->getText() << ')'; return; case Node::Kind::Module: if (Options.DisplayModuleNames) Printer << pointer->getText(); return; case Node::Kind::Identifier: Printer << pointer->getText(); return; case Node::Kind::Index: Printer << pointer->getIndex(); return; case Node::Kind::AutoClosureType: Printer << "@autoclosure "; printFunctionType(pointer); return; case Node::Kind::ThinFunctionType: Printer << "@convention(thin) "; printFunctionType(pointer); return; case Node::Kind::FunctionType: case Node::Kind::UncurriedFunctionType: printFunctionType(pointer); return; case Node::Kind::ArgumentTuple: { bool need_parens = false; if (pointer->getNumChildren() > 1) need_parens = true; else { if (!pointer->hasChildren()) need_parens = true; else { Node::Kind child0_kind = pointer->getChild(0)->getKind(); if (child0_kind == Node::Kind::Type) child0_kind = pointer->getChild(0)->getChild(0)->getKind(); if (child0_kind != Node::Kind::Tuple) need_parens = true; } } if (need_parens) Printer << "("; printChildren(pointer); if (need_parens) Printer << ")"; return; } case Node::Kind::Tuple: { Printer << "("; printChildren(pointer, ", "); Printer << ")"; return; } case Node::Kind::TupleElement: { unsigned Idx = 0; bool isVariadic = false; if (pointer->getNumChildren() >= 1 && pointer->getFirstChild()->getKind() == Node::Kind::VariadicMarker) { isVariadic = true; Idx++; } NodePointer type = nullptr; if (pointer->getNumChildren() == Idx + 1) { type = pointer->getChild(Idx); } else if (pointer->getNumChildren() == Idx + 2) { NodePointer id = pointer->getChild(Idx); type = pointer->getChild(Idx + 1); print(id); } if (isVariadic) { SugarType Sugar = findSugar(type); if (Sugar == SugarType::Array) type = type->getFirstChild()->getChild(1)->getFirstChild(); print(type); Printer << "..."; } else { print(type); } return; } case Node::Kind::TupleElementName: Printer << pointer->getText() << " : "; return; case Node::Kind::ReturnType: if (pointer->getNumChildren() == 0) Printer << " -> " << pointer->getText(); else { Printer << " -> "; printChildren(pointer); } return; case Node::Kind::Weak: Printer << "weak "; print(pointer->getChild(0)); return; case Node::Kind::Unowned: Printer << "unowned "; print(pointer->getChild(0)); return; case Node::Kind::Unmanaged: Printer << "unowned(unsafe) "; print(pointer->getChild(0)); return; case Node::Kind::InOut: Printer << "inout "; print(pointer->getChild(0)); return; case Node::Kind::NonObjCAttribute: Printer << "@nonobjc "; return; case Node::Kind::ObjCAttribute: Printer << "@objc "; return; case Node::Kind::DirectMethodReferenceAttribute: Printer << "super "; return; case Node::Kind::DynamicAttribute: Printer << "dynamic "; return; case Node::Kind::VTableAttribute: Printer << "override "; return; case Node::Kind::FunctionSignatureSpecialization: return printSpecializationPrefix(pointer, "function signature specialization"); case Node::Kind::GenericPartialSpecialization: return printSpecializationPrefix(pointer, "generic partial specialization", "Signature = "); case Node::Kind::GenericPartialSpecializationNotReAbstracted: return printSpecializationPrefix(pointer, "generic not-reabstracted partial specialization", "Signature = "); case Node::Kind::GenericSpecialization: return printSpecializationPrefix(pointer, "generic specialization"); case Node::Kind::GenericSpecializationNotReAbstracted: return printSpecializationPrefix(pointer, "generic not re-abstracted specialization"); case Node::Kind::SpecializationIsFragile: Printer << "preserving fragile attribute"; return; case Node::Kind::GenericSpecializationParam: print(pointer->getChild(0)); for (unsigned i = 1, e = pointer->getNumChildren(); i < e; ++i) { if (i == 1) Printer << " with "; else Printer << " and "; print(pointer->getChild(i)); } return; case Node::Kind::FunctionSignatureSpecializationParam: { uint64_t argNum = pointer->getIndex(); Printer << "Arg[" << argNum << "] = "; unsigned Idx = printFunctionSigSpecializationParam(pointer, 0); for (unsigned e = pointer->getNumChildren(); Idx < e;) { Printer << " and "; Idx = printFunctionSigSpecializationParam(pointer, Idx); } return; } case Node::Kind::FunctionSignatureSpecializationParamPayload: { std::string demangledName = demangleSymbolAsString(pointer->getText()); if (demangledName.empty()) { Printer << pointer->getText(); } else { Printer << demangledName; } return; } case Node::Kind::FunctionSignatureSpecializationParamKind: { uint64_t raw = pointer->getIndex(); bool printedOptionSet = false; if (raw & uint64_t(FunctionSigSpecializationParamKind::Dead)) { printedOptionSet = true; Printer << "Dead"; } if (raw & uint64_t(FunctionSigSpecializationParamKind::OwnedToGuaranteed)) { if (printedOptionSet) Printer << " and "; printedOptionSet = true; Printer << "Owned To Guaranteed"; } if (raw & uint64_t(FunctionSigSpecializationParamKind::SROA)) { if (printedOptionSet) Printer << " and "; Printer << "Exploded"; return; } if (printedOptionSet) return; switch (FunctionSigSpecializationParamKind(raw)) { case FunctionSigSpecializationParamKind::BoxToValue: Printer << "Value Promoted from Box"; break; case FunctionSigSpecializationParamKind::BoxToStack: Printer << "Stack Promoted from Box"; break; case FunctionSigSpecializationParamKind::ConstantPropFunction: Printer << "Constant Propagated Function"; break; case FunctionSigSpecializationParamKind::ConstantPropGlobal: Printer << "Constant Propagated Global"; break; case FunctionSigSpecializationParamKind::ConstantPropInteger: Printer << "Constant Propagated Integer"; break; case FunctionSigSpecializationParamKind::ConstantPropFloat: Printer << "Constant Propagated Float"; break; case FunctionSigSpecializationParamKind::ConstantPropString: Printer << "Constant Propagated String"; break; case FunctionSigSpecializationParamKind::ClosureProp: Printer << "Closure Propagated"; break; case FunctionSigSpecializationParamKind::Dead: case FunctionSigSpecializationParamKind::OwnedToGuaranteed: case FunctionSigSpecializationParamKind::SROA: printer_unreachable("option sets should have been handled earlier"); } return; } case Node::Kind::SpecializationPassID: Printer << pointer->getIndex(); return; case Node::Kind::BuiltinTypeName: Printer << pointer->getText(); return; case Node::Kind::Number: Printer << pointer->getIndex(); return; case Node::Kind::InfixOperator: Printer << pointer->getText() << " infix"; return; case Node::Kind::PrefixOperator: Printer << pointer->getText() << " prefix"; return; case Node::Kind::PostfixOperator: Printer << pointer->getText() << " postfix"; return; case Node::Kind::LazyProtocolWitnessTableAccessor: Printer << "lazy protocol witness table accessor for type "; print(pointer->getChild(0)); Printer << " and conformance "; print(pointer->getChild(1)); return; case Node::Kind::LazyProtocolWitnessTableCacheVariable: Printer << "lazy protocol witness table cache variable for type "; print(pointer->getChild(0)); Printer << " and conformance "; print(pointer->getChild(1)); return; case Node::Kind::ProtocolWitnessTableAccessor: Printer << "protocol witness table accessor for "; print(pointer->getFirstChild()); return; case Node::Kind::ProtocolWitnessTable: Printer << "protocol witness table for "; print(pointer->getFirstChild()); return; case Node::Kind::GenericProtocolWitnessTable: Printer << "generic protocol witness table for "; print(pointer->getFirstChild()); return; case Node::Kind::GenericProtocolWitnessTableInstantiationFunction: Printer << "instantiation function for generic protocol witness table for "; print(pointer->getFirstChild()); return; case Node::Kind::VTableThunk: { Printer << "vtable thunk for "; print(pointer->getChild(1)); Printer << " dispatching to "; print(pointer->getChild(0)); return; } case Node::Kind::ProtocolWitness: { Printer << "protocol witness for "; print(pointer->getChild(1)); Printer << " in conformance "; print(pointer->getChild(0)); return; } case Node::Kind::PartialApplyForwarder: if (Options.ShortenPartialApply) Printer << "partial apply"; else Printer << "partial apply forwarder"; if (pointer->hasChildren()) { Printer << " for "; print(pointer->getFirstChild()); } return; case Node::Kind::PartialApplyObjCForwarder: if (Options.ShortenPartialApply) Printer << "partial apply"; else Printer << "partial apply ObjC forwarder"; if (pointer->hasChildren()) { Printer << " for "; print(pointer->getFirstChild()); } return; case Node::Kind::FieldOffset: { print(pointer->getChild(0)); // directness Printer << "field offset for "; auto entity = pointer->getChild(1); print(entity, /*asContext*/ false, /*suppressType*/ !Options.DisplayTypeOfIVarFieldOffset); return; } case Node::Kind::ReabstractionThunk: case Node::Kind::ReabstractionThunkHelper: { if (Options.ShortenThunk) { Printer << "thunk for "; print(pointer->getChild(pointer->getNumChildren() - 2)); return; } Printer << "reabstraction thunk "; if (pointer->getKind() == Node::Kind::ReabstractionThunkHelper) Printer << "helper "; auto generics = getFirstChildOfKind(pointer, Node::Kind::DependentGenericSignature); assert(pointer->getNumChildren() == 2 + unsigned(generics != nullptr)); if (generics) { print(generics); Printer << " "; } Printer << "from "; print(pointer->getChild(pointer->getNumChildren() - 2)); Printer << " to "; print(pointer->getChild(pointer->getNumChildren() - 1)); return; } case Node::Kind::GenericTypeMetadataPattern: Printer << "generic type metadata pattern for "; print(pointer->getChild(0)); return; case Node::Kind::Metaclass: Printer << "metaclass for "; print(pointer->getFirstChild()); return; case Node::Kind::ProtocolDescriptor: Printer << "protocol descriptor for "; print(pointer->getChild(0)); return; case Node::Kind::FullTypeMetadata: Printer << "full type metadata for "; print(pointer->getChild(0)); return; case Node::Kind::TypeMetadata: Printer << "type metadata for "; print(pointer->getChild(0)); return; case Node::Kind::TypeMetadataAccessFunction: Printer << "type metadata accessor for "; print(pointer->getChild(0)); return; case Node::Kind::TypeMetadataLazyCache: Printer << "lazy cache variable for type metadata for "; print(pointer->getChild(0)); return; case Node::Kind::AssociatedTypeMetadataAccessor: Printer << "associated type metadata accessor for "; print(pointer->getChild(1)); Printer << " in "; print(pointer->getChild(0)); return; case Node::Kind::AssociatedTypeWitnessTableAccessor: Printer << "associated type witness table accessor for "; print(pointer->getChild(1)); Printer << " : "; print(pointer->getChild(2)); Printer << " in "; print(pointer->getChild(0)); return; case Node::Kind::NominalTypeDescriptor: Printer << "nominal type descriptor for "; print(pointer->getChild(0)); return; case Node::Kind::ValueWitness: Printer << toString(ValueWitnessKind(pointer->getIndex())); if (Options.ShortenValueWitness) Printer << " for "; else Printer << " value witness for "; print(pointer->getFirstChild()); return; case Node::Kind::ValueWitnessTable: Printer << "value witness table for "; print(pointer->getFirstChild()); return; case Node::Kind::WitnessTableOffset: Printer << "witness table offset for "; print(pointer->getFirstChild()); return; case Node::Kind::BoundGenericClass: case Node::Kind::BoundGenericStructure: case Node::Kind::BoundGenericEnum: printBoundGeneric(pointer); return; case Node::Kind::DynamicSelf: Printer << "Self"; return; case Node::Kind::CFunctionPointer: { Printer << "@convention(c) "; printFunctionType(pointer); return; } case Node::Kind::ObjCBlock: { Printer << "@convention(block) "; printFunctionType(pointer); return; } case Node::Kind::SILBoxType: { Printer << "@box "; NodePointer type = pointer->getChild(0); print(type); return; } case Node::Kind::Metatype: { unsigned Idx = 0; if (pointer->getNumChildren() == 2) { NodePointer repr = pointer->getChild(Idx); print(repr); Printer << " "; Idx++; } NodePointer type = pointer->getChild(Idx)->getChild(0); bool needs_parens = !isSimpleType(type); if (needs_parens) Printer << "("; print(type); if (needs_parens) Printer << ")"; if (isExistentialType(type)) { Printer << ".Protocol"; } else { Printer << ".Type"; } return; } case Node::Kind::ExistentialMetatype: { unsigned Idx = 0; if (pointer->getNumChildren() == 2) { NodePointer repr = pointer->getChild(Idx); print(repr); Printer << " "; Idx++; } NodePointer type = pointer->getChild(Idx); print(type); Printer << ".Type"; return; } case Node::Kind::MetatypeRepresentation: { Printer << pointer->getText(); return; } case Node::Kind::AssociatedTypeRef: print(pointer->getChild(0)); Printer << '.' << pointer->getChild(1)->getText(); return; case Node::Kind::ProtocolList: { NodePointer type_list = pointer->getChild(0); if (!type_list) return; if (type_list->getNumChildren() == 0) Printer << "Any"; else printChildren(type_list, " & "); return; } case Node::Kind::AssociatedType: // Don't print for now. return; case Node::Kind::QualifiedArchetype: { if (Options.ShortenArchetype) { Printer << "(archetype)"; return; } if (pointer->getNumChildren() < 2) return; NodePointer number = pointer->getChild(0); NodePointer decl_ctx = pointer->getChild(1); Printer << "(archetype " << number->getIndex() << " of "; print(decl_ctx); Printer << ")"; return; } case Node::Kind::OwningAddressor: printEntity(true, true, ".owningAddressor"); return; case Node::Kind::OwningMutableAddressor: printEntity(true, true, ".owningMutableAddressor"); return; case Node::Kind::NativeOwningAddressor: printEntity(true, true, ".nativeOwningAddressor"); return; case Node::Kind::NativeOwningMutableAddressor: printEntity(true, true, ".nativeOwningMutableAddressor"); return; case Node::Kind::NativePinningAddressor: printEntity(true, true, ".nativePinningAddressor"); return; case Node::Kind::NativePinningMutableAddressor: printEntity(true, true, ".nativePinningMutableAddressor"); return; case Node::Kind::UnsafeAddressor: printEntity(true, true, ".unsafeAddressor"); return; case Node::Kind::UnsafeMutableAddressor: printEntity(true, true, ".unsafeMutableAddressor"); return; case Node::Kind::GlobalGetter: printEntity(true, true, ".getter"); return; case Node::Kind::Getter: printEntity(true, true, ".getter"); return; case Node::Kind::Setter: printEntity(true, true, ".setter"); return; case Node::Kind::MaterializeForSet: printEntity(true, true, ".materializeForSet"); return; case Node::Kind::WillSet: printEntity(true, true, ".willset"); return; case Node::Kind::DidSet: printEntity(true, true, ".didset"); return; case Node::Kind::Allocator: printEntity(false, true, isClassType(pointer->getChild(0)) ? "__allocating_init" : "init"); return; case Node::Kind::Constructor: printEntity(false, true, "init"); return; case Node::Kind::Destructor: printEntity(false, false, "deinit"); return; case Node::Kind::Deallocator: printEntity(false, false, isClassType(pointer->getChild(0)) ? "__deallocating_deinit" : "deinit"); return; case Node::Kind::IVarInitializer: printEntity(false, false, "__ivar_initializer"); return; case Node::Kind::IVarDestroyer: printEntity(false, false, "__ivar_destroyer"); return; case Node::Kind::ProtocolConformance: { NodePointer child0 = pointer->getChild(0); NodePointer child1 = pointer->getChild(1); NodePointer child2 = pointer->getChild(2); if (pointer->getNumChildren() == 4) { // TODO: check if this is correct Printer << "property behavior storage of "; print(child2); Printer << " in "; print(child0); Printer << " : "; print(child1); } else { print(child0); if (Options.DisplayProtocolConformances) { Printer << " : "; print(child1); Printer << " in "; print(child2); } } return; } case Node::Kind::TypeList: printChildren(pointer); return; case Node::Kind::ImplConvention: Printer << pointer->getText(); return; case Node::Kind::ImplFunctionAttribute: Printer << pointer->getText(); return; case Node::Kind::ImplErrorResult: Printer << "@error "; LLVM_FALLTHROUGH; case Node::Kind::ImplParameter: case Node::Kind::ImplResult: printChildren(pointer, " "); return; case Node::Kind::ImplFunctionType: printImplFunctionType(pointer); return; case Node::Kind::ErrorType: Printer << ""; return; case Node::Kind::DependentPseudogenericSignature: case Node::Kind::DependentGenericSignature: { Printer << '<'; unsigned depth = 0; unsigned numChildren = pointer->getNumChildren(); for (; depth < numChildren && pointer->getChild(depth)->getKind() == Node::Kind::DependentGenericParamCount; ++depth) { if (depth != 0) Printer << "><"; unsigned count = pointer->getChild(depth)->getIndex(); for (unsigned index = 0; index < count; ++index) { if (index != 0) Printer << ", "; // FIXME: Depth won't match when a generic signature applies to a // method in generic type context. Printer << archetypeName(index, depth); } } if (depth != numChildren) { if (!Options.DisplayWhereClauses) { Printer << " where ..."; } else { Printer << " where "; for (unsigned i = depth; i < numChildren; ++i) { if (i > depth) Printer << ", "; print(pointer->getChild(i)); } } } Printer << '>'; return; } case Node::Kind::DependentGenericParamCount: printer_unreachable("should be printed as a child of a " "DependentGenericSignature"); case Node::Kind::DependentGenericConformanceRequirement: { NodePointer type = pointer->getChild(0); NodePointer reqt = pointer->getChild(1); print(type); Printer << ": "; print(reqt); return; } case Node::Kind::DependentGenericLayoutRequirement: { NodePointer type = pointer->getChild(0); NodePointer layout = pointer->getChild(1); print(type); Printer << ": "; assert(layout->getKind() == Node::Kind::Identifier); assert(layout->getText().size() == 1); char c = layout->getText()[0]; StringRef name; if (c == 'U') { name = "_UnknownLayout"; } else if (c == 'R') { name = "_RefCountedObject"; } else if (c == 'N') { name = "_NativeRefCountedObject"; } else if (c == 'C') { name = "_Class"; } else if (c == 'D') { name = "_NativeClass"; } else if (c == 'T') { name = "_Trivial"; } else if (c == 'E' || c == 'e') { name = "_Trivial"; } else if (c == 'M' || c == 'm') { name = "_TrivialAtMost"; } Printer << name; if (pointer->getNumChildren() > 2) { Printer << "("; print(pointer->getChild(2)); if (pointer->getNumChildren() > 3) { Printer << ", "; print(pointer->getChild(3)); } Printer << ")"; } return; } case Node::Kind::DependentGenericSameTypeRequirement: { NodePointer fst = pointer->getChild(0); NodePointer snd = pointer->getChild(1); print(fst); Printer << " == "; print(snd); return; } case Node::Kind::DependentGenericParamType: { Printer << pointer->getText(); return; } case Node::Kind::DependentGenericType: { NodePointer sig = pointer->getChild(0); NodePointer depTy = pointer->getChild(1); print(sig); Printer << ' '; print(depTy); return; } case Node::Kind::DependentMemberType: { NodePointer base = pointer->getChild(0); print(base); Printer << '.'; NodePointer assocTy = pointer->getChild(1); print(assocTy); return; } case Node::Kind::DependentAssociatedTypeRef: { Printer << pointer->getText(); return; } case Node::Kind::ReflectionMetadataBuiltinDescriptor: Printer << "reflection metadata builtin descriptor "; print(pointer->getChild(0)); return; case Node::Kind::ReflectionMetadataFieldDescriptor: Printer << "reflection metadata field descriptor "; print(pointer->getChild(0)); return; case Node::Kind::ReflectionMetadataAssocTypeDescriptor: Printer << "reflection metadata associated type descriptor "; print(pointer->getChild(0)); return; case Node::Kind::ReflectionMetadataSuperclassDescriptor: Printer << "reflection metadata superclass descriptor "; print(pointer->getChild(0)); return; case Node::Kind::ThrowsAnnotation: Printer<< " throws "; return; case Node::Kind::EmptyList: Printer << " empty-list "; return; case Node::Kind::FirstElementMarker: Printer << " first-element-marker "; return; case Node::Kind::VariadicMarker: Printer << " variadic-marker "; return; case Node::Kind::SILBoxTypeWithLayout: { assert(pointer->getNumChildren() == 1 || pointer->getNumChildren() == 3); NodePointer layout = pointer->getChild(0); assert(layout->getKind() == Node::Kind::SILBoxLayout); NodePointer signature, genericArgs = nullptr; if (pointer->getNumChildren() == 3) { signature = pointer->getChild(1); assert(signature->getKind() == Node::Kind::DependentGenericSignature); genericArgs = pointer->getChild(2); assert(genericArgs->getKind() == Node::Kind::TypeList); print(signature); Printer << ' '; } print(layout); if (genericArgs) { Printer << " <"; for (unsigned i = 0, e = genericArgs->getNumChildren(); i < e; ++i) { if (i > 0) Printer << ", "; print(genericArgs->getChild(i)); } Printer << '>'; } return; } case Node::Kind::SILBoxLayout: Printer << '{'; for (unsigned i = 0; i < pointer->getNumChildren(); ++i) { if (i > 0) Printer << ','; Printer << ' '; print(pointer->getChild(i)); } Printer << " }"; return; case Node::Kind::SILBoxImmutableField: case Node::Kind::SILBoxMutableField: Printer << (pointer->getKind() == Node::Kind::SILBoxImmutableField ? "let " : "var "); assert(pointer->getNumChildren() == 1 && pointer->getChild(0)->getKind() == Node::Kind::Type); print(pointer->getChild(0)); return; } printer_unreachable("bad node kind!"); } std::string Demangle::nodeToString(NodePointer root, const DemangleOptions &options) { if (!root) return ""; return NodePrinter(options).printRoot(root); }