Skip to content

Enable nullable reference types for generated models#1709

Open
Copilot wants to merge 4 commits intomasterfrom
copilot/add-nullables-support
Open

Enable nullable reference types for generated models#1709
Copilot wants to merge 4 commits intomasterfrom
copilot/add-nullables-support

Conversation

Copy link
Contributor

Copilot AI commented Dec 26, 2025

Collection properties like VolumeMounts are uninitialized by default, causing runtime NullReferenceException when accessed without null checks. The type system provides no indication which properties may be null.

Changes

  • Enable nullable context: Added <Nullable>enable</Nullable> to Directory.Build.props

  • Update code generator: Modified TypeHelper.cs to emit nullable annotations:

    • Collections: IList<T>? (nullable collection, non-nullable items)
    • Optional properties: string?, V1Lifecycle?
    • Required properties: string Name (non-nullable, per Kubernetes spec)
  • Add tests: 5 tests verifying nullable behavior for collections, required properties, and optional properties

Example

Before:

var container = new V1Container();
container.VolumeMounts.Add(new V1VolumeMount()); // Runtime NullReferenceException

After:

var container = new V1Container();
// Compiler warning: VolumeMounts might be null
container.VolumeMounts = new List<V1VolumeMount>
{
    new V1VolumeMount { Name = "vol1", MountPath = "/data" }
};

Developers now receive compile-time warnings for null access, reducing runtime errors and making the API contract explicit.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • crl.comodoca.com
    • Triggering command: /home/REDACTED/work/csharp/.codeql-scratch/dbs/csharp/working/.dotnet/dotnet /home/REDACTED/work/csharp/.codeql-scratch/dbs/csharp/working/.dotnet/dotnet restore --no-dependencies /home/REDACTED/work/csharp/csharp/src/KubernetesClient.Classic/KubernetesClient.Classic.csproj --packages /home/REDACTED/work/csharp/.codeql-scratch/dbs/csharp/working/packages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal /p:TargetFrameworkRootPath=/home/REDACTED/work/csharp/.codeql-scratch/dbs/csharp/working/emptyFakeDotnetRoot /p:NetCoreTargetingPackRoot=/home/REDACTED/work/csharp/.codeql-scratch/dbs/csharp/working/emptyFakeDotnetRoot (dns block)
  • ocsp.comodoca.com
    • Triggering command: /home/REDACTED/work/csharp/.codeql-scratch/dbs/csharp/working/.dotnet/dotnet /home/REDACTED/work/csharp/.codeql-scratch/dbs/csharp/working/.dotnet/dotnet restore --no-dependencies /home/REDACTED/work/csharp/csharp/src/KubernetesClient.Classic/KubernetesClient.Classic.csproj --packages /home/REDACTED/work/csharp/.codeql-scratch/dbs/csharp/working/packages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal /p:TargetFrameworkRootPath=/home/REDACTED/work/csharp/.codeql-scratch/dbs/csharp/working/emptyFakeDotnetRoot /p:NetCoreTargetingPackRoot=/home/REDACTED/work/csharp/.codeql-scratch/dbs/csharp/working/emptyFakeDotnetRoot (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>Nullables feature request</issue_title>
<issue_description>Could the library start using nullables in the source code? Otherwise its really hard to know what is initialized and what not or you need to check everything.

For example:

var container = new V1Container();
container.VolumeMounts.Add(new V1VolumeMount());

produces:
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.</issue_description>

Comments on the Issue (you are @copilot in this section)

@tg123 i agree and i am also attempting to migrate everything to `struct`

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@k8s-ci-robot k8s-ci-robot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Dec 26, 2025
Co-authored-by: tg123 <170430+tg123@users.noreply.github.com>
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Copilot
Once this PR has been reviewed and has the lgtm label, please ask for approval from tg123. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added size/M Denotes a PR that changes 30-99 lines, ignoring generated files. and removed size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. labels Dec 26, 2025
Co-authored-by: tg123 <170430+tg123@users.noreply.github.com>
@k8s-ci-robot k8s-ci-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Dec 26, 2025
Co-authored-by: tg123 <170430+tg123@users.noreply.github.com>
Copilot AI changed the title [WIP] Add nullables support to source code Enable nullable reference types for generated models Dec 26, 2025
Copilot AI requested a review from tg123 December 26, 2025 08:50
@brendandburns brendandburns marked this pull request as ready for review February 4, 2026 20:04
Copilot AI review requested due to automatic review settings February 4, 2026 20:04
@k8s-ci-robot k8s-ci-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Feb 4, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enables nullable reference types for generated Kubernetes client models to address runtime NullReferenceException errors when accessing uninitialized collection properties. The type system will now explicitly indicate which properties may be null through compile-time warnings.

Changes:

  • Enabled nullable context globally in Directory.Build.props
  • Updated the code generator (TypeHelper.cs) to emit nullable annotations for collections, optional properties, and required properties
  • Added 5 unit tests verifying nullable behavior for various property types

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
Directory.Build.props Enables nullable reference types globally for all projects
src/LibKubernetesGenerator/TypeHelper.cs Updates type generation logic to add nullable annotations: collections are nullable, items are non-nullable, required properties use C# 11 required modifier
tests/KubernetesClient.Tests/NullableReferenceTypesTests.cs Adds tests verifying nullable behavior for container properties including collections, required fields, and optional fields
Comments suppressed due to low confidence (2)

tests/KubernetesClient.Tests/NullableReferenceTypesTests.cs:65

  • If Name is a required property on V1Container (as suggested by the test name and comments), this test will fail to compile because required properties must be initialized. Either initialize Name in the object initializer or verify that Name is not actually required in the Kubernetes schema.
            var container = new V1Container();

src/LibKubernetesGenerator/TypeHelper.cs:129

  • The comment on line 128 is misleading. It states "Collection items are always non-nullable, unless we're at the root level", but the actual logic at line 129 makes items non-nullable in two cases: when isCollectionItem is true OR when parent.IsRequired is true. Consider updating the comment to accurately reflect the implementation, for example: "Collection items and required properties are non-nullable".
                    // Collection items are always non-nullable, unless we're at the root level
                    return (isCollectionItem || parent.IsRequired) ? typeName : typeName + "?";

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<LangVersion>13.0</LangVersion>
<Nullable>enable</Nullable>
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enabling nullable reference types globally in Directory.Build.props will affect all projects in the solution, including existing handwritten code that may not be nullable-aware. This is a significant breaking change that could introduce numerous compiler warnings throughout the codebase. Consider:

  1. Verifying that all projects build without warnings or document the expected warnings
  2. Providing a migration guide for consumers of the library
  3. Considering a phased approach where nullable is enabled only for generated code initially using #nullable enable directives in generated files
  4. If global nullable enablement is intended, ensure all existing code has been reviewed for nullable compliance

This is a major architectural decision that could impact maintainability and should be carefully validated before merging.

Copilot uses AI. Check for mistakes.
public void ContainerImageIsOptionalProperty()
{
// Arrange & Act
var container = new V1Container();
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the Name property of V1Container is marked as required in the Kubernetes schema (which seems likely based on test ContainerNameIsRequiredProperty), this test will fail to compile because required properties must be initialized in object initializers. Consider either:

  1. Initializing Name in the object initializer: var container = new V1Container { Name = "test-container" };
  2. Using the SetsRequiredMembers attribute if this is intentional for testing purposes
  3. Verifying whether Name is actually required in the Kubernetes schema

The same issue may affect line 65 in the ContainerLifecycleIsOptionalComplexProperty test.

Copilot uses AI. Check for mistakes.
Comment on lines +117 to +154
return $"IList<{GetDotNetType(schema.Item, parent, isCollectionItem: true)}>?";
}

if (schema.IsDictionary && schema.AdditionalPropertiesSchema != null)
{
return $"IDictionary<string, {GetDotNetType(schema.AdditionalPropertiesSchema, parent)}>";
return $"IDictionary<string, {GetDotNetType(schema.AdditionalPropertiesSchema, parent, isCollectionItem: true)}>?";
}

if (schema?.Reference != null)
if (schema.Reference != null)
{
return classNameHelper.GetClassNameForSchemaDefinition(schema.Reference);
var typeName = classNameHelper.GetClassNameForSchemaDefinition(schema.Reference);
// Collection items are always non-nullable, unless we're at the root level
return (isCollectionItem || parent.IsRequired) ? typeName : typeName + "?";
}

if (schema != null)
{
return GetDotNetType(schema.Type, parent.Name, parent.IsRequired, schema.Format);
}
return GetDotNetType(schema.Type, parent.Name, isCollectionItem || parent.IsRequired, schema.Format);
}

return GetDotNetType(parent.Type, parent.Name, parent.IsRequired, parent.Format);
return GetDotNetType(parent.Type, parent.Name, isCollectionItem || parent.IsRequired, parent.Format);
}

public string GetDotNetType(JsonSchemaProperty p)
{
if (p.Reference != null)
{
return classNameHelper.GetClassNameForSchemaDefinition(p.Reference);
var typeName = classNameHelper.GetClassNameForSchemaDefinition(p.Reference);
return p.IsRequired ? typeName : typeName + "?";
}

if (p.IsArray)
if (p.IsArray && p.Item != null)
{
// getType
return $"IList<{GetDotNetType(p.Item, p)}>";
// getType - items in arrays are non-nullable
return $"IList<{GetDotNetType(p.Item, p, isCollectionItem: true)}>?";
}

if (p.IsDictionary && p.AdditionalPropertiesSchema != null)
{
return $"IDictionary<string, {GetDotNetType(p.AdditionalPropertiesSchema, p)}>";
return $"IDictionary<string, {GetDotNetType(p.AdditionalPropertiesSchema, p, isCollectionItem: true)}>?";
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current implementation always makes collections nullable (lines 117, 122, 149, 154) regardless of whether they are top-level properties or items within another collection. This means nested collections like IList of IList of string would be typed as IList<IList?>? where the inner lists are nullable.

However, the design goal stated in the PR is that collection items should be non-nullable. For consistency, nested collections that are collection items should probably be non-nullable. Consider modifying the logic to:

  • Make collections nullable only when they are top-level properties (not collection items)
  • For example, line 117 could be: return isCollectionItem ? $"IList&lt;{{...}}&gt;" : $"IList<{{...}}>?";

This would ensure IList<IList?> where the inner collection is non-nullable (cannot have null inner lists), while the items in those inner lists could still be nullable if needed.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nullables feature request

3 participants