docs: private ValidationResult isValidClassForType(Class<?> clazz, SoyType type) {
      // Exit early if the class is primitive and the type is nullable -- that's not allowed.
      // Then remove null from the type.  This allows us to accept precise params for nullable
      // types, e.g, for int|null we can allow IntegerData (which will be passed as 'null').
      if (SoyTypes.isNullable(type) && Primitives.allPrimitiveTypes().contains(clazz)) {
        return ValidationResult.forNullToPrimitive(type);
      }
      // Also exit early if the type is a VE, since those aren't allowed as params.
      if (SoyTypes.isKindOrUnionOfKind(type, SoyType.Kind.VE)
          || SoyTypes.isKindOrUnionOfKind(type, SoyType.Kind.VE_DATA)) {
        return ValidationResult.ve(type);
      }
  
      ImmutableSet<Class<?>> expectedClasses = null;
      GenericDescriptor expectedDescriptor = null;
      type = SoyTypes.tryRemoveNull(type);
      switch (type.getKind()) {
        case ANY:
        case UNKNOWN:
          expectedClasses = UNKNOWN_TYPES;
          break;
        case ATTRIBUTES:
        case CSS:
        case HTML:
        case URI:
        case TRUSTED_RESOURCE_URI:
        case JS:
          expectedClasses = SANITIZED_TYPES;
          break;
        case BOOL:
          expectedClasses = BOOL_TYPES;
          break;
        case FLOAT:
          expectedClasses = FLOAT_TYPES;
          break;
        case INT:
          expectedClasses = INT_TYPES;
          break;
        case LEGACY_OBJECT_MAP:
          expectedClasses = LEGACY_OBJECT_MAP_TYPES;
          break;
        case LIST:
          expectedClasses = LIST_TYPES;
          break;
        case MAP:
          expectedClasses = MAP_TYPES;
          break;
        case RECORD:
          expectedClasses = RECORD_TYPES;
          break;
        case STRING:
          expectedClasses = STRING_TYPES;
          break;
        case NULL:
          expectedClasses = NULL_TYPES;
          break;
        case PROTO:
          expectedClasses = PROTO_TYPES;
          expectedDescriptor = ((SoyProtoType) type).getDescriptor();
          break;
        case PROTO_ENUM:
          expectedClasses = PROTO_ENUM_TYPES;
          expectedDescriptor = ((SoyProtoEnumType) type).getDescriptor();
          break;
        case UNION:
          // number is a special case, it should work for double and NumberData
          if (type.equals(SoyTypes.NUMBER_TYPE)) {
            expectedClasses = NUMBER_TYPES;
            break;
          }
          // If this is a union, make sure the type is valid for every member.
          // If the type isn't valid for any member, then there's no guarantee this will work
          // for an arbitrary template at runtime.
          ValidationResult result = ValidationResult.valid();
          for (SoyType member : ((UnionType) type).getMembers()) {
            result.merge(isValidClassForType(clazz, member));
          }
          return result;
        case VE:
        case VE_DATA:
          throw new IllegalStateException("This should have been caught above");
        case ERROR:
          throw new IllegalStateException("Cannot have error type from function signature");
      }
  
      checkState(expectedClasses != null, "expectedClass not set!");
      if (expectedClasses.contains(clazz)) {
        return ValidationResult.valid();
      }
      ImmutableSet<String> expectedDescriptorNames = ImmutableSet.of();
      if (expectedDescriptor instanceof Descriptor) {
        expectedDescriptorNames =
            ImmutableSet.of(JavaQualifiedNames.getClassName((Descriptor) expectedDescriptor));
        if (matchesProtoDescriptor(Message.class, clazz, expectedDescriptor)) {
          return ValidationResult.valid();
        }
      }
      if (expectedDescriptor instanceof EnumDescriptor) {
        expectedDescriptorNames =
            ImmutableSet.of(JavaQualifiedNames.getClassName((EnumDescriptor) expectedDescriptor));
        if (clazz.isEnum()
            && matchesProtoDescriptor(ProtocolMessageEnum.class, clazz, expectedDescriptor)) {
          return ValidationResult.valid();
        }
      }
      // If none of the above conditions match, we failed.
      return ValidationResult.invalid(
          Stream.concat(
                  expectedClasses.stream().map(Class::getName), expectedDescriptorNames.stream())
              .collect(toImmutableSet()));
    }