public void printAttribute(String label, Attribute attr) {
    if (attr == null) {
      printNull(label);
    } else {
      printString(label, attr.getClass().getSimpleName());

      indent(+1);
      attr.accept(attrVisitor);
      indent(-1);
    }
  }
  /**
   * Extend suppression sets for both {@code @SuppressWarnings} and custom suppression annotations.
   * When we explore a new node, we have to extend the suppression sets with any new suppressed
   * warnings or custom suppression annotations. We also have to retain the previous suppression set
   * so that we can reinstate it when we move up the tree.
   *
   * <p>We do not modify the existing suppression sets, so they can be restored when moving up the
   * tree. We also avoid copying the suppression sets if the next node to explore does not have any
   * suppressed warnings or custom suppression annotations. This is the common case.
   *
   * @param sym The {@code Symbol} for the AST node currently being scanned
   * @param suppressWarningsType The {@code Type} for {@code @SuppressWarnings}, as given by javac's
   *     symbol table
   * @param suppressionsOnCurrentPath The set of strings in all {@code @SuppressWarnings}
   *     annotations on the current path through the AST
   * @param customSuppressionsOnCurrentPath The set of all custom suppression annotations
   */
  public NewSuppressions extendSuppressionSets(
      Symbol sym,
      Type suppressWarningsType,
      Set<String> suppressionsOnCurrentPath,
      Set<Class<? extends Annotation>> customSuppressionsOnCurrentPath,
      boolean inGeneratedCode) {

    boolean newInGeneratedCode = inGeneratedCode || ASTHelpers.hasAnnotation(sym, Generated.class);

    /** Handle custom suppression annotations. */
    Set<Class<? extends Annotation>> newCustomSuppressions = null;
    for (Class<? extends Annotation> annotationType : customSuppressionAnnotations) {
      if (ASTHelpers.hasAnnotation(sym, annotationType)) {
        if (newCustomSuppressions == null) {
          newCustomSuppressions = new HashSet<>(customSuppressionsOnCurrentPath);
        }
        newCustomSuppressions.add(annotationType);
      }
    }

    /** Handle @SuppressWarnings. */
    Set<String> newSuppressions = null;
    // Iterate over annotations on this symbol, looking for SuppressWarnings
    for (Attribute.Compound attr : sym.getAnnotationMirrors()) {
      // TODO(eaftan): use JavacElements.getAnnotation instead
      if (attr.type.tsym == suppressWarningsType.tsym) {
        for (List<Pair<MethodSymbol, Attribute>> v = attr.values; v.nonEmpty(); v = v.tail) {
          Pair<MethodSymbol, Attribute> value = v.head;
          if (value.fst.name.toString().equals("value"))
            if (value.snd instanceof Attribute.Array) { // SuppressWarnings takes an array
              for (Attribute suppress : ((Attribute.Array) value.snd).values) {
                if (newSuppressions == null) {
                  newSuppressions = new HashSet<>(suppressionsOnCurrentPath);
                }
                // TODO(eaftan): check return value to see if this was a new warning?
                newSuppressions.add((String) suppress.getValue());
              }
            } else {
              throw new RuntimeException("Expected SuppressWarnings annotation to take array type");
            }
        }
      }
    }

    return new NewSuppressions(newSuppressions, newCustomSuppressions, newInGeneratedCode);
  }