{"record":{"id":"959961e19581ce0c","repo":"stride3d/stride","slug":"type-0-is-not-a-primitive","errorCode":null,"errorMessage":"Type [{0}] is not a primitive","messagePattern":"Type \\[(.+?)\\] is not a primitive","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"sources/core/Stride.Core.Reflection/TypeDescriptors/NullableDescriptor.cs","lineNumber":25,"sourceCode":"\n/// <summary>\n/// Describes a descriptor for a nullable type <see cref=\"Nullable{T}\"/>.\n/// </summary>\npublic class NullableDescriptor : ObjectDescriptor\n{\n    private static readonly List<IMemberDescriptor> EmptyMembers = [];\n\n    /// <summary>\n    /// Initializes a new instance of the <see cref=\"ObjectDescriptor\" /> class.\n    /// </summary>\n    /// <param name=\"factory\">The factory.</param>\n    /// <param name=\"type\">The type.</param>\n    /// <exception cref=\"ArgumentException\">Type [{0}] is not a primitive</exception>\n    public NullableDescriptor(ITypeDescriptorFactory factory, Type type, bool emitDefaultValues, IMemberNamingConvention namingConvention)\n        : base(factory, type, emitDefaultValues, namingConvention)\n    {\n        if (!IsNullable(type))\n            throw new ArgumentException(\"Type [{0}] is not a primitive\");\n\n        UnderlyingType = Nullable.GetUnderlyingType(type)!;\n    }\n\n    public override DescriptorCategory Category => DescriptorCategory.Nullable;\n\n    /// <summary>\n    /// Gets the type underlying type T of the nullable <see cref=\"Nullable{T}\"/>\n    /// </summary>\n    /// <value>The type of the element.</value>\n    public Type UnderlyingType { get; }\n\n    /// <summary>\n    /// Determines whether the specified type is nullable.\n    /// </summary>\n    /// <param name=\"type\">The type.</param>\n    /// <returns><c>true</c> if the specified type is nullable; otherwise, <c>false</c>.</returns>\n    public static bool IsNullable(Type type)","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/core/Stride.Core.Reflection/TypeDescriptors/NullableDescriptor.cs#L7-L43","documentation":"The NullableDescriptor constructor only accepts Nullable<T> types (i.e. typeof(int?) and similar); it verifies with IsNullable(type) and throws ArgumentException 'Type [{0}] is not a primitive' when a non-nullable type is passed. Note the message uses '{0}' without string.Format, so the raw placeholder is displayed. This is an internal TypeDescriptor factory invariant — descriptors are built per-type by TypeDescriptorFactory.","triggerScenarios":"Passing a non-Nullable<T> type (e.g. int, string, or a custom struct not wrapped in Nullable<>) to NullableDescriptor's constructor, or having the descriptor factory route a type to NullableDescriptor incorrectly.","commonSituations":"Custom TypeDescriptorFactory registrations or reflection-based asset serialization scanning where type dispatch logic misclassifies a type as nullable; hand-constructed descriptors in unit tests.","solutions":["Check Nullable.GetUnderlyingType(type) != null before constructing NullableDescriptor.","Route non-nullable types to ObjectDescriptor/PrimitiveDescriptor via the normal TypeDescriptorFactory instead.","Fix the dispatch predicate so only Nullable<T> types reach this constructor."],"exampleFix":"// before\nvar descriptor = new NullableDescriptor(factory, typeof(int), false, namingConvention);\n// after\nif (Nullable.GetUnderlyingType(typeof(int?)) != null)\n    var descriptor = new NullableDescriptor(factory, typeof(int?), false, namingConvention);","handlingStrategy":"validation","validationCode":"if (Nullable.GetUnderlyingType(type) == null)\n    throw new InvalidOperationException($\"{type} is not Nullable<T>; use the appropriate descriptor\");\nvar d = new NullableDescriptor(factory, type, emitDefaultValues, namingConvention);","typeGuard":"static bool IsNullableType(Type t) => Nullable.GetUnderlyingType(t) != null;","tryCatchPattern":"try { return new NullableDescriptor(factory, type, emit, conv); }\ncatch (ArgumentException) { return TypeDescriptorFactory.Default.FindDescriptor(type); }","preventionTips":["Check Nullable.GetUnderlyingType before constructing nullable descriptors.","Let TypeDescriptorFactory dispatch types instead of instantiating descriptors manually.","Add a unit test asserting descriptor construction per type category."],"tags":["reflection","constructor","nullable","csharp"],"backgroundTag":"invalid-constructor-argument","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}