//===--- IndexSymbol.cpp - Types and functions for indexing symbols -------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include "clang/Index/IndexSymbol.h"
#include "clang/AST/Attr.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/Lex/MacroInfo.h"

using namespace clang;
using namespace clang::index;

/// \returns true if \c D is a subclass of 'XCTestCase'.
static bool isUnitTestCase(const ObjCInterfaceDecl *D) {
  if (!D)
    return false;
  while (const ObjCInterfaceDecl *SuperD = D->getSuperClass()) {
    if (SuperD->getName() == "XCTestCase")
      return true;
    D = SuperD;
  }
  return false;
}

/// \returns true if \c D is in a subclass of 'XCTestCase', returns void, has
/// no parameters, and its name starts with 'test'.
static bool isUnitTest(const ObjCMethodDecl *D) {
  if (!D->parameters().empty())
    return false;
  if (!D->getReturnType()->isVoidType())
    return false;
  if (!D->getSelector().getNameForSlot(0).startswith("test"))
    return false;
  return isUnitTestCase(D->getClassInterface());
}

static void checkForIBOutlets(const Decl *D, SymbolPropertySet &PropSet) {
  if (D->hasAttr<IBOutletAttr>()) {
    PropSet |= (SymbolPropertySet)SymbolProperty::IBAnnotated;
  } else if (D->hasAttr<IBOutletCollectionAttr>()) {
    PropSet |= (SymbolPropertySet)SymbolProperty::IBAnnotated;
    PropSet |= (SymbolPropertySet)SymbolProperty::IBOutletCollection;
  }
}

bool index::isFunctionLocalSymbol(const Decl *D) {
  assert(D);

  if (isa<ParmVarDecl>(D))
    return true;

  if (isa<ObjCTypeParamDecl>(D))
    return true;

  if (isa<UsingDirectiveDecl>(D))
    return false;
  if (!D->getParentFunctionOrMethod())
    return false;

  if (const NamedDecl *ND = dyn_cast<NamedDecl>(D)) {
    switch (ND->getFormalLinkage()) {
      case NoLinkage:
      case InternalLinkage:
        return true;
      case VisibleNoLinkage:
      case UniqueExternalLinkage:
      case ModuleInternalLinkage:
        llvm_unreachable("Not a sema linkage");
      case ModuleLinkage:
      case ExternalLinkage:
        return false;
    }
  }

  return true;
}

SymbolInfo index::getSymbolInfo(const Decl *D) {
  assert(D);
  SymbolInfo Info;
  Info.Kind = SymbolKind::Unknown;
  Info.SubKind = SymbolSubKind::None;
  Info.Properties = SymbolPropertySet();
  Info.Lang = SymbolLanguage::C;

  if (isFunctionLocalSymbol(D)) {
    Info.Properties |= (SymbolPropertySet)SymbolProperty::Local;
  }
  if (isa<ObjCProtocolDecl>(D->getDeclContext())) {
    Info.Properties |= (SymbolPropertySet)SymbolProperty::ProtocolInterface;
  }

  if (auto *VT = dyn_cast<VarTemplateDecl>(D)) {
    Info.Properties |= (SymbolPropertySet)SymbolProperty::Generic;
    Info.Lang = SymbolLanguage::CXX;
    // All other fields are filled from the templated decl.
    D = VT->getTemplatedDecl();
  }

  if (const TagDecl *TD = dyn_cast<TagDecl>(D)) {
    switch (TD->getTagKind()) {
    case TTK_Struct:
      Info.Kind = SymbolKind::Struct; break;
    case TTK_Union:
      Info.Kind = SymbolKind::Union; break;
    case TTK_Class:
      Info.Kind = SymbolKind::Class;
      Info.Lang = SymbolLanguage::CXX;
      break;
    case TTK_Interface:
      Info.Kind = SymbolKind::Protocol;
      Info.Lang = SymbolLanguage::CXX;
      break;
    case TTK_Enum:
      Info.Kind = SymbolKind::Enum; break;
    }

    if (const CXXRecordDecl *CXXRec = dyn_cast<CXXRecordDecl>(D)) {
      if (!CXXRec->isCLike()) {
        Info.Lang = SymbolLanguage::CXX;
        if (CXXRec->getDescribedClassTemplate()) {
          Info.Properties |= (SymbolPropertySet)SymbolProperty::Generic;
        }
      }
    }

    if (isa<ClassTemplatePartialSpecializationDecl>(D)) {
      Info.Properties |= (SymbolPropertySet)SymbolProperty::Generic;
      Info.Properties |=
          (SymbolPropertySet)SymbolProperty::TemplatePartialSpecialization;
    } else if (isa<ClassTemplateSpecializationDecl>(D)) {
      Info.Properties |= (SymbolPropertySet)SymbolProperty::Generic;
      Info.Properties |=
          (SymbolPropertySet)SymbolProperty::TemplateSpecialization;
    }

  } else if (auto *VD = dyn_cast<VarDecl>(D)) {
    Info.Kind = SymbolKind::Variable;
    if (isa<ParmVarDecl>(D)) {
      Info.Kind = SymbolKind::Parameter;
    } else if (isa<CXXRecordDecl>(D->getDeclContext())) {
      Info.Kind = SymbolKind::StaticProperty;
      Info.Lang = SymbolLanguage::CXX;
    }

    if (isa<VarTemplatePartialSpecializationDecl>(D)) {
      Info.Lang = SymbolLanguage::CXX;
      Info.Properties |= (SymbolPropertySet)SymbolProperty::Generic;
      Info.Properties |=
          (SymbolPropertySet)SymbolProperty::TemplatePartialSpecialization;
    } else if (isa<VarTemplateSpecializationDecl>(D)) {
      Info.Lang = SymbolLanguage::CXX;
      Info.Properties |= (SymbolPropertySet)SymbolProperty::Generic;
      Info.Properties |=
          (SymbolPropertySet)SymbolProperty::TemplateSpecialization;
    } else if (VD->getDescribedVarTemplate()) {
      Info.Lang = SymbolLanguage::CXX;
      Info.Properties |= (SymbolPropertySet)SymbolProperty::Generic;
    }

  } else {
    switch (D->getKind()) {
    case Decl::Import:
      Info.Kind = SymbolKind::Module;
      break;
    case Decl::Typedef:
      Info.Kind = SymbolKind::TypeAlias; break; // Lang = C
    case Decl::Function:
      Info.Kind = SymbolKind::Function;
      break;
    case Decl::Field:
    case Decl::IndirectField:
      Info.Kind = SymbolKind::Field;
      if (const CXXRecordDecl *
            CXXRec = dyn_cast<CXXRecordDecl>(D->getDeclContext())) {
        if (!CXXRec->isCLike())
          Info.Lang = SymbolLanguage::CXX;
      }
      break;
    case Decl::EnumConstant:
      Info.Kind = SymbolKind::EnumConstant; break;
    case Decl::ObjCInterface:
    case Decl::ObjCImplementation: {
      Info.Kind = SymbolKind::Class;
      Info.Lang = SymbolLanguage::ObjC;
      const ObjCInterfaceDecl *ClsD = dyn_cast<ObjCInterfaceDecl>(D);
      if (!ClsD)
        ClsD = cast<ObjCImplementationDecl>(D)->getClassInterface();
      if (isUnitTestCase(ClsD))
        Info.Properties |= (SymbolPropertySet)SymbolProperty::UnitTest;
      break;
    }
    case Decl::ObjCProtocol:
      Info.Kind = SymbolKind::Protocol;
      Info.Lang = SymbolLanguage::ObjC;
      break;
    case Decl::ObjCCategory:
    case Decl::ObjCCategoryImpl: {
      Info.Kind = SymbolKind::Extension;
      Info.Lang = SymbolLanguage::ObjC;
      const ObjCInterfaceDecl *ClsD = nullptr;
      if (auto *CatD = dyn_cast<ObjCCategoryDecl>(D))
        ClsD = CatD->getClassInterface();
      else
        ClsD = cast<ObjCCategoryImplDecl>(D)->getClassInterface();
      if (isUnitTestCase(ClsD))
        Info.Properties |= (SymbolPropertySet)SymbolProperty::UnitTest;
      break;
    }
    case Decl::ObjCMethod: {
      const ObjCMethodDecl *MD = cast<ObjCMethodDecl>(D);
      Info.Kind = MD->isInstanceMethod() ? SymbolKind::InstanceMethod : SymbolKind::ClassMethod;
      if (MD->isPropertyAccessor()) {
        if (MD->param_size())
          Info.SubKind = SymbolSubKind::AccessorSetter;
        else
          Info.SubKind = SymbolSubKind::AccessorGetter;
      }
      Info.Lang = SymbolLanguage::ObjC;
      if (isUnitTest(MD))
        Info.Properties |= (SymbolPropertySet)SymbolProperty::UnitTest;
      if (D->hasAttr<IBActionAttr>())
        Info.Properties |= (SymbolPropertySet)SymbolProperty::IBAnnotated;
      break;
    }
    case Decl::ObjCProperty:
      Info.Kind = SymbolKind::InstanceProperty;
      Info.Lang = SymbolLanguage::ObjC;
      checkForIBOutlets(D, Info.Properties);
      if (auto *Annot = D->getAttr<AnnotateAttr>()) {
        if (Annot->getAnnotation() == "gk_inspectable")
          Info.Properties |= (SymbolPropertySet)SymbolProperty::GKInspectable;
      }
      break;
    case Decl::ObjCIvar:
      Info.Kind = SymbolKind::Field;
      Info.Lang = SymbolLanguage::ObjC;
      checkForIBOutlets(D, Info.Properties);
      break;
    case Decl::Namespace:
      Info.Kind = SymbolKind::Namespace;
      Info.Lang = SymbolLanguage::CXX;
      break;
    case Decl::NamespaceAlias:
      Info.Kind = SymbolKind::NamespaceAlias;
      Info.Lang = SymbolLanguage::CXX;
      break;
    case Decl::CXXConstructor: {
      Info.Kind = SymbolKind::Constructor;
      Info.Lang = SymbolLanguage::CXX;
      auto *CD = cast<CXXConstructorDecl>(D);
      if (CD->isCopyConstructor())
        Info.SubKind = SymbolSubKind::CXXCopyConstructor;
      else if (CD->isMoveConstructor())
        Info.SubKind = SymbolSubKind::CXXMoveConstructor;
      break;
    }
    case Decl::CXXDestructor:
      Info.Kind = SymbolKind::Destructor;
      Info.Lang = SymbolLanguage::CXX;
      break;
    case Decl::CXXConversion:
      Info.Kind = SymbolKind::ConversionFunction;
      Info.Lang = SymbolLanguage::CXX;
      break;
    case Decl::CXXMethod: {
      const CXXMethodDecl *MD = cast<CXXMethodDecl>(D);
      if (MD->isStatic())
        Info.Kind = SymbolKind::StaticMethod;
      else
        Info.Kind = SymbolKind::InstanceMethod;
      Info.Lang = SymbolLanguage::CXX;
      break;
    }
    case Decl::ClassTemplate:
      Info.Kind = SymbolKind::Class;
      Info.Properties |= (SymbolPropertySet)SymbolProperty::Generic;
      Info.Lang = SymbolLanguage::CXX;
      break;
    case Decl::FunctionTemplate:
      Info.Kind = SymbolKind::Function;
      Info.Properties |= (SymbolPropertySet)SymbolProperty::Generic;
      Info.Lang = SymbolLanguage::CXX;
      if (const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(
                           cast<FunctionTemplateDecl>(D)->getTemplatedDecl())) {
        if (isa<CXXConstructorDecl>(MD))
          Info.Kind = SymbolKind::Constructor;
        else if (isa<CXXDestructorDecl>(MD))
          Info.Kind = SymbolKind::Destructor;
        else if (isa<CXXConversionDecl>(MD))
          Info.Kind = SymbolKind::ConversionFunction;
        else {
          if (MD->isStatic())
            Info.Kind = SymbolKind::StaticMethod;
          else
            Info.Kind = SymbolKind::InstanceMethod;
        }
      }
      break;
    case Decl::TypeAliasTemplate:
      Info.Kind = SymbolKind::TypeAlias;
      Info.Lang = SymbolLanguage::CXX;
      Info.Properties |= (SymbolPropertySet)SymbolProperty::Generic;
      break;
    case Decl::TypeAlias:
      Info.Kind = SymbolKind::TypeAlias;
      Info.Lang = SymbolLanguage::CXX;
      break;
    case Decl::UnresolvedUsingTypename:
      Info.Kind = SymbolKind::Using;
      Info.SubKind = SymbolSubKind::UsingTypename;
      Info.Lang = SymbolLanguage::CXX;
      Info.Properties |= (SymbolPropertySet)SymbolProperty::Generic;
      break;
    case Decl::UnresolvedUsingValue:
      Info.Kind = SymbolKind::Using;
      Info.SubKind = SymbolSubKind::UsingValue;
      Info.Lang = SymbolLanguage::CXX;
      Info.Properties |= (SymbolPropertySet)SymbolProperty::Generic;
      break;
    case Decl::Using:
      Info.Kind = SymbolKind::Using;
      Info.Lang = SymbolLanguage::CXX;
      break;
    case Decl::Binding:
      Info.Kind = SymbolKind::Variable;
      Info.Lang = SymbolLanguage::CXX;
      break;
    case Decl::MSProperty:
      Info.Kind = SymbolKind::InstanceProperty;
      if (const CXXRecordDecl *CXXRec =
              dyn_cast<CXXRecordDecl>(D->getDeclContext())) {
        if (!CXXRec->isCLike())
          Info.Lang = SymbolLanguage::CXX;
      }
      break;
    case Decl::ClassTemplatePartialSpecialization:
    case Decl::ClassScopeFunctionSpecialization:
    case Decl::ClassTemplateSpecialization:
    case Decl::CXXRecord:
    case Decl::Enum:
    case Decl::Record:
      llvm_unreachable("records handled before");
      break;
    case Decl::VarTemplateSpecialization:
    case Decl::VarTemplatePartialSpecialization:
    case Decl::ImplicitParam:
    case Decl::ParmVar:
    case Decl::Var:
    case Decl::VarTemplate:
      llvm_unreachable("variables handled before");
      break;
    case Decl::TemplateTypeParm:
      Info.Kind = SymbolKind::TemplateTypeParm;
      break;
    case Decl::TemplateTemplateParm:
      Info.Kind = SymbolKind::TemplateTemplateParm;
      break;
    case Decl::NonTypeTemplateParm:
      Info.Kind = SymbolKind::NonTypeTemplateParm;
      break;
    // Other decls get the 'unknown' kind.
    default:
      break;
    }
  }

  if (Info.Kind == SymbolKind::Unknown)
    return Info;

  if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
    if (FD->getTemplatedKind() ==
          FunctionDecl::TK_FunctionTemplateSpecialization) {
      Info.Properties |= (SymbolPropertySet)SymbolProperty::Generic;
      Info.Properties |=
          (SymbolPropertySet)SymbolProperty::TemplateSpecialization;
    }
  }

  if (Info.Properties & (SymbolPropertySet)SymbolProperty::Generic)
    Info.Lang = SymbolLanguage::CXX;

  if (auto *attr = D->getExternalSourceSymbolAttr()) {
    if (attr->getLanguage() == "Swift")
      Info.Lang = SymbolLanguage::Swift;
  }

  return Info;
}

SymbolInfo index::getSymbolInfoForMacro(const MacroInfo &) {
  SymbolInfo Info;
  Info.Kind = SymbolKind::Macro;
  Info.SubKind = SymbolSubKind::None;
  Info.Properties = SymbolPropertySet();
  Info.Lang = SymbolLanguage::C;
  return Info;
}

bool index::applyForEachSymbolRoleInterruptible(SymbolRoleSet Roles,
                                   llvm::function_ref<bool(SymbolRole)> Fn) {
#define APPLY_FOR_ROLE(Role) \
  if (Roles & (unsigned)SymbolRole::Role) \
    if (!Fn(SymbolRole::Role)) \
      return false;

  APPLY_FOR_ROLE(Declaration);
  APPLY_FOR_ROLE(Definition);
  APPLY_FOR_ROLE(Reference);
  APPLY_FOR_ROLE(Read);
  APPLY_FOR_ROLE(Write);
  APPLY_FOR_ROLE(Call);
  APPLY_FOR_ROLE(Dynamic);
  APPLY_FOR_ROLE(AddressOf);
  APPLY_FOR_ROLE(Implicit);
  APPLY_FOR_ROLE(Undefinition);
  APPLY_FOR_ROLE(RelationChildOf);
  APPLY_FOR_ROLE(RelationBaseOf);
  APPLY_FOR_ROLE(RelationOverrideOf);
  APPLY_FOR_ROLE(RelationReceivedBy);
  APPLY_FOR_ROLE(RelationCalledBy);
  APPLY_FOR_ROLE(RelationExtendedBy);
  APPLY_FOR_ROLE(RelationAccessorOf);
  APPLY_FOR_ROLE(RelationContainedBy);
  APPLY_FOR_ROLE(RelationIBTypeOf);
  APPLY_FOR_ROLE(RelationSpecializationOf);
  APPLY_FOR_ROLE(NameReference);

#undef APPLY_FOR_ROLE

  return true;
}

void index::applyForEachSymbolRole(SymbolRoleSet Roles,
                                   llvm::function_ref<void(SymbolRole)> Fn) {
  applyForEachSymbolRoleInterruptible(Roles, [&](SymbolRole r) -> bool {
    Fn(r);
    return true;
  });
}

void index::printSymbolRoles(SymbolRoleSet Roles, raw_ostream &OS) {
  bool VisitedOnce = false;
  applyForEachSymbolRole(Roles, [&](SymbolRole Role) {
    if (VisitedOnce)
      OS << ',';
    else
      VisitedOnce = true;
    switch (Role) {
    case SymbolRole::Declaration: OS << "Decl"; break;
    case SymbolRole::Definition: OS << "Def"; break;
    case SymbolRole::Reference: OS << "Ref"; break;
    case SymbolRole::Read: OS << "Read"; break;
    case SymbolRole::Write: OS << "Writ"; break;
    case SymbolRole::Call: OS << "Call"; break;
    case SymbolRole::Dynamic: OS << "Dyn"; break;
    case SymbolRole::AddressOf: OS << "Addr"; break;
    case SymbolRole::Implicit: OS << "Impl"; break;
    case SymbolRole::Undefinition: OS << "Undef"; break;
    case SymbolRole::RelationChildOf: OS << "RelChild"; break;
    case SymbolRole::RelationBaseOf: OS << "RelBase"; break;
    case SymbolRole::RelationOverrideOf: OS << "RelOver"; break;
    case SymbolRole::RelationReceivedBy: OS << "RelRec"; break;
    case SymbolRole::RelationCalledBy: OS << "RelCall"; break;
    case SymbolRole::RelationExtendedBy: OS << "RelExt"; break;
    case SymbolRole::RelationAccessorOf: OS << "RelAcc"; break;
    case SymbolRole::RelationContainedBy: OS << "RelCont"; break;
    case SymbolRole::RelationIBTypeOf: OS << "RelIBType"; break;
    case SymbolRole::RelationSpecializationOf: OS << "RelSpecialization"; break;
    case SymbolRole::NameReference: OS << "NameReference"; break;
    }
  });
}

bool index::printSymbolName(const Decl *D, const LangOptions &LO,
                            raw_ostream &OS) {
  if (auto *ND = dyn_cast<NamedDecl>(D)) {
    PrintingPolicy Policy(LO);
    // Forward references can have different template argument names. Suppress
    // the template argument names in constructors to make their name more
    // stable.
    Policy.SuppressTemplateArgsInCXXConstructors = true;
    DeclarationName DeclName = ND->getDeclName();
    if (DeclName.isEmpty())
      return true;
    DeclName.print(OS, Policy);
    return false;
  } else {
    return true;
  }
}

StringRef index::getSymbolKindString(SymbolKind K) {
  switch (K) {
  case SymbolKind::Unknown: return "<unknown>";
  case SymbolKind::Module: return "module";
  case SymbolKind::Namespace: return "namespace";
  case SymbolKind::NamespaceAlias: return "namespace-alias";
  case SymbolKind::Macro: return "macro";
  case SymbolKind::Enum: return "enum";
  case SymbolKind::Struct: return "struct";
  case SymbolKind::Class: return "class";
  case SymbolKind::Protocol: return "protocol";
  case SymbolKind::Extension: return "extension";
  case SymbolKind::Union: return "union";
  case SymbolKind::TypeAlias: return "type-alias";
  case SymbolKind::Function: return "function";
  case SymbolKind::Variable: return "variable";
  case SymbolKind::Field: return "field";
  case SymbolKind::EnumConstant: return "enumerator";
  case SymbolKind::InstanceMethod: return "instance-method";
  case SymbolKind::ClassMethod: return "class-method";
  case SymbolKind::StaticMethod: return "static-method";
  case SymbolKind::InstanceProperty: return "instance-property";
  case SymbolKind::ClassProperty: return "class-property";
  case SymbolKind::StaticProperty: return "static-property";
  case SymbolKind::Constructor: return "constructor";
  case SymbolKind::Destructor: return "destructor";
  case SymbolKind::ConversionFunction: return "conversion-func";
  case SymbolKind::Parameter: return "param";
  case SymbolKind::Using: return "using";
  case SymbolKind::TemplateTypeParm: return "template-type-param";
  case SymbolKind::TemplateTemplateParm: return "template-template-param";
  case SymbolKind::NonTypeTemplateParm: return "non-type-template-param";
  }
  llvm_unreachable("invalid symbol kind");
}

StringRef index::getSymbolSubKindString(SymbolSubKind K) {
  switch (K) {
  case SymbolSubKind::None: return "<none>";
  case SymbolSubKind::CXXCopyConstructor: return "cxx-copy-ctor";
  case SymbolSubKind::CXXMoveConstructor: return "cxx-move-ctor";
  case SymbolSubKind::AccessorGetter: return "acc-get";
  case SymbolSubKind::AccessorSetter: return "acc-set";
  case SymbolSubKind::UsingTypename: return "using-typename";
  case SymbolSubKind::UsingValue: return "using-value";
  }
  llvm_unreachable("invalid symbol subkind");
}

StringRef index::getSymbolLanguageString(SymbolLanguage K) {
  switch (K) {
  case SymbolLanguage::C: return "C";
  case SymbolLanguage::ObjC: return "ObjC";
  case SymbolLanguage::CXX: return "C++";
  case SymbolLanguage::Swift: return "Swift";
  }
  llvm_unreachable("invalid symbol language kind");
}

void index::applyForEachSymbolProperty(SymbolPropertySet Props,
                                  llvm::function_ref<void(SymbolProperty)> Fn) {
#define APPLY_FOR_PROPERTY(K)                                                  \
  if (Props & (SymbolPropertySet)SymbolProperty::K)                            \
  Fn(SymbolProperty::K)

  APPLY_FOR_PROPERTY(Generic);
  APPLY_FOR_PROPERTY(TemplatePartialSpecialization);
  APPLY_FOR_PROPERTY(TemplateSpecialization);
  APPLY_FOR_PROPERTY(UnitTest);
  APPLY_FOR_PROPERTY(IBAnnotated);
  APPLY_FOR_PROPERTY(IBOutletCollection);
  APPLY_FOR_PROPERTY(GKInspectable);
  APPLY_FOR_PROPERTY(Local);
  APPLY_FOR_PROPERTY(ProtocolInterface);

#undef APPLY_FOR_PROPERTY
}

void index::printSymbolProperties(SymbolPropertySet Props, raw_ostream &OS) {
  bool VisitedOnce = false;
  applyForEachSymbolProperty(Props, [&](SymbolProperty Prop) {
    if (VisitedOnce)
      OS << ',';
    else
      VisitedOnce = true;
    switch (Prop) {
    case SymbolProperty::Generic: OS << "Gen"; break;
    case SymbolProperty::TemplatePartialSpecialization: OS << "TPS"; break;
    case SymbolProperty::TemplateSpecialization: OS << "TS"; break;
    case SymbolProperty::UnitTest: OS << "test"; break;
    case SymbolProperty::IBAnnotated: OS << "IB"; break;
    case SymbolProperty::IBOutletCollection: OS << "IBColl"; break;
    case SymbolProperty::GKInspectable: OS << "GKI"; break;
    case SymbolProperty::Local: OS << "local"; break;
    case SymbolProperty::ProtocolInterface: OS << "protocol"; break;
    }
  });
}
