ChilliCream / ChilliCream/graphql-platform
[BindMember] is silently ignored on source-generated [ObjectType<T>]
@michaelstaib is already working on this.
Since Sep 14, 2026.
- Dominant language
- C#
- Stars
- 5.8k
- Forks
- 810
- Avg merge
- 15h 39m
- Merged PRs (30d)
- 98
Description
Product
Hot Chocolate
Version
16.6.4
Link to minimal reproduction
https://github.com/drapka/hc-v16-bind-member-ignored-on-object-type/blob/main/repro.cs
Steps to reproduce
- Clone the repro repository
- Run dotnet repro.cs from the project root
What is expected?
Widget.size present and resolver-backed, exactly like Doohickey.size.
What is actually happening?
Widget.size is stripped from the schema.
Relevant log output
{ widget { size } } -> "The field `size` does not exist on the type `Widget`."
{ doohickey { size } } -> { "data": { "doohickey": { "size": 2 } } }
Additional context
Migrating a type extension from [ExtendObjectType<T>] to [ObjectType<T>] - which the HC0096 analyzer hint asks you to do - silently breaks every [BindMember]-bound resolver in the class. No diagnostic, no exception, no schema error: the affected fields just change or disappear.
TypeFileBuilderBase.WriteResolverBindings treats MemberBindingKind.Property ([BindMember]) and MemberBindingKind.Field ([BindField]) asymmetrically:
For a property binding it emits an Ignore() for the bound member's field name but derives the resolver's own field name from the method name, not from the binding - the fieldBinding lookup on line 237 only ever matches MemberBindingKind.Field.
For a field binding it emits no Ignore() and uses the binding name for the resolver.
Generated code for the broken case (add #:property EmitCompilerGeneratedFiles=true to the repro):
var ignoredFields = new HashSet<string>();
ignoredFields.Add(naming.GetMemberName("Size", MemberKind.ObjectField)); // "size"
foreach (string fieldName in ignoredFields)
{
descriptor.Field(fieldName).Ignore(); // ignores "size"
}
descriptor
.Field(naming.GetMemberName("Size", MemberKind.ObjectField)) // "size" again
.ExtendWith(static (field, context) => { /* resolver */ }, ...);
ObjectTypeDescriptor.Field(string) is get-or-create by name, so both calls return the same ObjectFieldDescriptor. Ignore() sets Configuration.Ignore = true and ExtendWith never clears it - so the field the resolver was just attached to is already marked ignored, and it is dropped during type completion.
When the method name does not derive to the bound name (ComputeSize), the two calls hit two different descriptors: size stays ignored and the resolver lands on a brand-new computeSize field. Either way the [BindMember] contract - this resolver replaces Prop's field - is not honored.
[ExtendObjectType<T>] is unaffected because it is applied at runtime by ObjectTypeExtensionHelper, never through this generator.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.