Describe the bug
When C# generation uses a previous package through ApiCompatVersion, a required nullable reference-type model property can become nonnullable inside the generator and gain an Argument.AssertNotNull guard.
This was reproduced in openai/openai-dotnet#1341. The TypeSpec declaration remains:
parameters: unknown | null;
Before enabling ApiCompatVersion, the generated FunctionTool constructor accepts null. With the same TypeSpec blob and the same Microsoft.TypeSpec.Generator.ClientModel build, enabling ApiCompatVersion adds:
Argument.AssertNotNull(functionParameters, nameof(functionParameters));
The custom factory and serialization paths still support null, so the generated public constructor becomes inconsistent with both the wire contract and the other creation path.
Root cause
LastContractView obtains reference types from Roslyn through TypeSymbolExtensions.GetCSharpType, which does not retain nullable-reference annotations. A nullable-oblivious last-contract BinaryData is therefore represented with IsNullable == false.
ModelProvider.BuildProperties treats that as a meaningful nullability difference and replaces the current nullable TypeSpec-derived property type with the last-contract type. When the lazy constructor parameter is later created, ParameterProvider.GetParameterValidation sees a nonnullable reference type and emits AssertNotNull.
Scope
This can affect any required nullable reference-type model property when last-contract compatibility is enabled. The same OpenAI regeneration also added a guard for a bytes | null property. Nullable collection properties can additionally lose null-safe constructor conversion.
Expected behavior
Nullable-oblivious last-contract reference types should not override current TypeSpec reference nullability. Value-type nullability compatibility should remain unchanged.
Suggested coverage
- required
unknown | null against an oblivious BinaryData last-contract property
- required
bytes | null
- required nullable collection conversion
- required nonnullable reference property as a negative case
Related review: openai/openai-dotnet#1341 (comment)
Describe the bug
When C# generation uses a previous package through
ApiCompatVersion, a required nullable reference-type model property can become nonnullable inside the generator and gain anArgument.AssertNotNullguard.This was reproduced in openai/openai-dotnet#1341. The TypeSpec declaration remains:
Before enabling
ApiCompatVersion, the generatedFunctionToolconstructor acceptsnull. With the same TypeSpec blob and the sameMicrosoft.TypeSpec.Generator.ClientModelbuild, enablingApiCompatVersionadds:The custom factory and serialization paths still support
null, so the generated public constructor becomes inconsistent with both the wire contract and the other creation path.Root cause
LastContractViewobtains reference types from Roslyn throughTypeSymbolExtensions.GetCSharpType, which does not retain nullable-reference annotations. A nullable-oblivious last-contractBinaryDatais therefore represented withIsNullable == false.ModelProvider.BuildPropertiestreats that as a meaningful nullability difference and replaces the current nullable TypeSpec-derived property type with the last-contract type. When the lazy constructor parameter is later created,ParameterProvider.GetParameterValidationsees a nonnullable reference type and emitsAssertNotNull.Scope
This can affect any required nullable reference-type model property when last-contract compatibility is enabled. The same OpenAI regeneration also added a guard for a
bytes | nullproperty. Nullable collection properties can additionally lose null-safe constructor conversion.Expected behavior
Nullable-oblivious last-contract reference types should not override current TypeSpec reference nullability. Value-type nullability compatibility should remain unchanged.
Suggested coverage
unknown | nullagainst an obliviousBinaryDatalast-contract propertybytes | nullRelated review: openai/openai-dotnet#1341 (comment)