XINCGer/Unity3DTraining · error · DescriptorValidationException

Field with primitive type has type_name.

Error message

Field with primitive type has type_name.

What it means

Fields with a primitive/scalar type (numbers, bool, string, bytes) must not set type_name; only message and enum fields use it. During CrossLink, if a field has no inline type resolution but carries a TypeName and its field type is scalar, DescriptorValidationException is thrown.

Solutions

  1. Clear Proto.TypeName for scalar-typed fields before building the descriptor set.
  2. Regenerate descriptors from the .proto so protoc emits type_name only for message/enum fields.
  3. Sanitize imported FileDescriptorProtos: drop type_name whenever Type is a primitive type.

Example fix

// before
new FieldDescriptorProto { Name = "count", Type = TYPE_INT32, TypeName = "MyApp.Count" }
// after
new FieldDescriptorProto { Name = "count", Type = TYPE_INT32 }
Defensive patterns

Strategy: validation

Validate before calling

var scalar = field.Proto.Type is FieldDescriptorProto.Types.Type.Int32 or FieldDescriptorProto.Types.Type.Int64 or FieldDescriptorProto.Types.Type.Uint32 or FieldDescriptorProto.Types.Type.Uint64 or FieldDescriptorProto.Types.Type.Float or FieldDescriptorProto.Types.Type.Double or FieldDescriptorProto.Types.Type.Bool or FieldDescriptorProto.Types.Type.String or FieldDescriptorProto.Types.Type.Bytes;
if (scalar && field.Proto.TypeName != "")
    throw new InvalidDataException("Scalar field " + field.Name + " must not set type_name");

Type guard

bool TypeNameAllowed(FieldDescriptorProto f) =>
    f.Type == FieldDescriptorProto.Types.Type.Message || f.Type == FieldDescriptorProto.Types.Type.Enum || f.TypeName == "";

Try / catch

try { descriptor = FileDescriptor.BuildFromByteStrings(data); }
catch (DescriptorValidationException ex) when (ex.Message == "Field with primitive type has type_name.")
{ log.LogError(ex, "Clear type_name on scalar fields"); }

Prevention

When it happens

Trigger: A FieldDescriptorProto with a scalar Type (e.g. TYPE_INT32) AND a non-empty TypeName — e.g. hand-built protos that set both, or generated descriptors where type_name was not cleared when the type was changed to a scalar.

Common situations: Changing a field from a message to a scalar in code without clearing TypeName; custom code generators emitting TypeName for all fields; descriptor manipulation tools that copy field templates.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of XINCGer/Unity3DTraining@016f98412e (2026-09-12). Data as JSON: /api/errors/8b069c3ba06b79b5. Report an issue: GitHub.

Appendix: source

Thrown at NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/Reflection/FieldDescriptor.cs:344

                    }
                    messageType = (MessageDescriptor)typeDescriptor;

                    if (Proto.DefaultValue != "")
                    {
                        throw new DescriptorValidationException(this, "Messages can't have default values.");
                    }
                }
                else if (fieldType == FieldType.Enum)
                {
                    if (!(typeDescriptor is EnumDescriptor))
                    {
                        throw new DescriptorValidationException(this, "\"" + Proto.TypeName + "\" is not an enum type.");
                    }
                    enumType = (EnumDescriptor)typeDescriptor;
                }
                else
                {
                    throw new DescriptorValidationException(this, "Field with primitive type has type_name.");
                }
            }
            else
            {
                if (fieldType == FieldType.Message || fieldType == FieldType.Enum)
                {
                    throw new DescriptorValidationException(this, "Field with message or enum type missing type_name.");
                }
            }

            // Note: no attempt to perform any default value parsing

            File.DescriptorPool.AddFieldByNumber(this);

            if (ContainingType != null && ContainingType.Proto.Options != null && ContainingType.Proto.Options.MessageSetWireFormat)
            {
                throw new DescriptorValidationException(this, "MessageSet format is not supported.");
            }

View on GitHub (pinned to 016f98412e)