docs: public static void extractPlaceholders(ClassNode node, Map<GenericsTypeName, GenericsType> map) {
          if (node == null) return;
  
          if (node.isArray()) {
              extractPlaceholders(node.getComponentType(), map);
              return;
          }
  
          if (!node.isUsingGenerics() || !node.isRedirectNode()) return;
          GenericsType[] parameterized = node.getGenericsTypes();
          if (parameterized == null || parameterized.length == 0) return;
          GenericsType[] redirectGenericsTypes = node.redirect().getGenericsTypes();
          if (redirectGenericsTypes == null ||
                  (node.isGenericsPlaceHolder() && redirectGenericsTypes.length != parameterized.length) /* GROOVY-8609 */ ) {
              redirectGenericsTypes = parameterized;
          }
          if (redirectGenericsTypes.length != parameterized.length) {
              throw new GroovyBugError("Expected earlier checking to detect generics parameter arity mismatch" +
                      "\nExpected: " + node.getName() + toGenericTypesString(redirectGenericsTypes) +
                      "\nSupplied: " + node.getName() + toGenericTypesString(parameterized));
          }
  
          List<GenericsType> valueList = new LinkedList<>();
          for (int i = 0; i < redirectGenericsTypes.length; i++) {
              GenericsType redirectType = redirectGenericsTypes[i];
              if (redirectType.isPlaceholder()) {
                  GenericsTypeName name = new GenericsTypeName(redirectType.getName());
                  if (!map.containsKey(name)) {
                      GenericsType value = parameterized[i];
                      map.put(name, value);
  
                      valueList.add(value);
                  }
              }
          }
  
          for (GenericsType value : valueList) {
              if (value.isWildcard()) {
                  ClassNode lowerBound = value.getLowerBound();
                  if (lowerBound != null) {
                      extractPlaceholders(lowerBound, map);
                  }
                  ClassNode[] upperBounds = value.getUpperBounds();
                  if (upperBounds != null) {
                      for (ClassNode upperBound : upperBounds) {
                          extractPlaceholders(upperBound, map);
                      }
                  }
              } else if (!value.isPlaceholder()) {
                  extractPlaceholders(value.getType(), map);
              }
          }
      }