Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial attempt at removing explicit #nullable enable in source #44936

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

MiYanni
Copy link
Member

@MiYanni MiYanni commented Nov 18, 2024

Related: #25920

Summary

I started working on this in March and aborted the idea because I hit a point where thousands of changes needed to occur. There has been a resurgence in trying to get the repo to be Nullable enable everywhere.

I started by removing #nullable enable in any source files. Then, I attempted to make those projects use <Nullable>enable</Nullable>. There were many issues with this, but in the end, to make the build work, any shared source files I had to add:

#pragma warning disable IDE0240
#nullable enable
#pragma warning restore IDE0240

This is because those files are now shared between both nullable and non-nullable projects. So, this is a stopgap to getting to a point where nullable is enabled everywhere and no source files contain #nullable enable.

Problems

When making projects <Nullable>enable</Nullable>, there are several chain reactions that can occur.

  • Classes referenced from packages using nullability can cause your callsites to those classes to become invalid
  • Interfaces that require changes in nullability cause all implementors to also need to change
  • Base classes that require changes can potentially affect derived classes
  • Shared source files is incredibly painful since there's no way to resolve problems other than either:
    • Using #nullable enable in the file, or
    • Changing the project to <Nullable>enable</Nullable>
  • Logic can be intentionally (or unintentionally) handling null or you need to add custom logic to handle the null situation
    • Sometimes, you need to manually unwind the callstack to determine where exactly null should be handled
    • Sometimes, just adding ? to everything is really detrimental as you can cause more work for yourself than just handling null in a strategic location
    • ! should be used sparingly and intentionally. For example, string.IsNullOrEmpty is not recognized as a null check, so you might need to help the analyzers by putting ! on uses of the variable after that point.
      • After some discussion, we opted to avoid the use of ! entirely. In this PR, I've converted several uses of string.IsNullOrEmpty to a simple null and length check. The only reason this is required is because we build for both .NET (core) and .NET Framework. The Framework version of this method does not have the attribute that makes it aware of being a null-check. This also applies to string.IsNullOrWhitespace. However, my resolution for this was to just add an additional null check prior to the method call, which isn't costly.
  • string used as a property where you don't know if string.Empty is an acceptable value
    • If you leave it as a non-nullable property, it needs to be set at class instantiation, where previously, it did not. Therefore, you tend to add ? but this can cause a chain reaction of null checking necessary throughout the class and callers of that class and its properties.

Projects now using <Nullable>enable</Nullable>

  • Microsoft.DotNet.Cli.Utils.csproj
  • Microsoft.DotNet.Configurer.csproj
  • Microsoft.DotNet.NativeWrapper.csproj
  • Microsoft.DotNet.SdkResolver.csproj
  • Microsoft.NET.Build.Extensions.Tasks.csproj
  • Microsoft.NET.Sdk.Publish.Tasks.csproj

Other changes

  • If I noticed typos/spelling errors, I'd usually fix them
  • Some old files needed to be touched, so I tried to clean them up as I was fixing their nullability
  • I added at least one record to replace passing around a tuple
  • Some shared source files were able to have the #nullable enable removed as all projects they were shared with became nullable-enabled projects

Going forward

After some discussion, it seems like the forward strategy will be:

  1. Have #nullable enable at the top of every shared source file (follow-up PR)
  2. Progressively convert projects by adding <Nullable>enable</Nullable> to them
  3. When all projects are converted, add <Nullable>enable</Nullable> to the main Directory.Build.props, remove it from every .csproj, and remove #nullable enable from the shared source files

…xing projects previously using these statements to now use <Nullable>enable</Nullable>. Got the Microsoft.DotNet.Cli.Utils.csproj building. More to come.
… nullable enable. Added WorkloadRootPath.cs since the tuples were problematic with nullability logic.
…m/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md#nullable-reference-type-support Converted Microsoft.DotNet.Configurer.csproj to nullable. Realized the other projects that need nullable are massive, so putting this on the backburner.
# Conflicts:
#	src/Cli/Microsoft.DotNet.Cli.Utils/Microsoft.DotNet.Cli.Utils.csproj
#	src/Cli/Microsoft.DotNet.Cli.Utils/UILanguageOverride.cs
#	src/Cli/dotnet/dotnet.csproj
#	src/Common/WorkloadFileBasedInstall.cs
#	src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs
#	src/Resolvers/Microsoft.DotNet.NativeWrapper/Microsoft.DotNet.NativeWrapper.csproj
#	src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/SdkDirectoryWorkloadManifestProvider.cs
#	src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/WorkloadInstallType.cs
#	src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/WorkloadResolver.cs
#	src/Tasks/Common/NuGetUtils.cs
#	src/WebSdk/Publish/Tasks/Microsoft.NET.Sdk.Publish.Tasks.csproj
#	src/WebSdk/Publish/Tasks/MsDeploy/CommonUtility.cs
#	src/WebSdk/Publish/Tasks/Tasks/MsDeploy/MSDeploy.cs
#	src/WebSdk/Publish/Tasks/Tasks/MsDeploy/VsMsdeploy.cs
#	src/WebSdk/Publish/Tasks/Tasks/WebJobs/GenerateRunCommandFile.cs
#	src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/HttpClientHelpers.cs
#	src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/HttpResponseMessageWrapper.cs
#	src/WebSdk/Publish/Tasks/WebJobsCommandGenerator.cs
…ccidental removal of nullable on the WorkloadManifestReader.
@MiYanni MiYanni requested review from dsplaisted and a team November 18, 2024 22:37
@MiYanni MiYanni requested review from a team and vijayrkn as code owners November 18, 2024 22:37
Copy link

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged Request triage from a team member label Nov 18, 2024
Copy link

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.


namespace Microsoft.NET.Sdk.WorkloadManifestReader
{
public record WorkloadRootPath(string? Path, bool Installable);
Copy link
Member Author

Choose a reason for hiding this comment

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

I added this record as there was a tuple being passed around previously. Made more sense to use an actual defined type.

Comment on lines -4 to -8
#pragma warning disable IDE0240 // Nullable directive is redundant (when file is included to a project that already enables nullable

#nullable enable


Copy link
Member Author

Choose a reason for hiding this comment

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

This is a shared source file. However, the 2 projects that use it are already nullable enabled projects.
image

@RikkiGibson
Copy link
Contributor

RikkiGibson commented Nov 22, 2024

Roslyn uses some msbuild config to suppress nullable warnings on non-netcore targets.

This allows running nullable analysis against only the netcore versions of core library methods like string.IsNullOrEmpty, where they are annotated for nullable analysis for work properly with them. This would eliminate the need to use ! in some cases in this PR.

See property DisableNullableWarnings and its usage: https://github.com/dotnet/roslyn/blob/fe4c98aafc5bd0fe0455b9de381930fe89646ab4/eng/targets/Imports.targets#L43

Possibly, dotnet/sdk may wish to do something similar.

@MiYanni
Copy link
Member Author

MiYanni commented Nov 23, 2024

@RikkiGibson I'm going through and manually resolving all ! uses. It'll be in my next commit for this PR. I decided just to use manual null and Length checks or combine a null check with string.IsNullOrWhitespace since double checking null is minimal impact. If the uses of those checks is really problematic, we could consider that option later down the line.

…Other minor adjustments based on PR feedback.
@jaredpar
Copy link
Member

jaredpar commented Nov 27, 2024

Have #nullable enable at the top of every shared source file (follow-up PR)

Another approach to consider is:

  1. Put #nullable disable at the top of every source file that is not currently part of project with <Nullable>enable</Nullable>.
  2. Put <Nullable>enable</Nullable> in Directory.Build.props and remove it in every project.

This approach has a couple of advantages:

  • Makes it clear at a glance whether a source file has nullability enabled or not. Unless it's suppressed at the top of the file then it's enabled
  • This is the only mass refactoring you need to make on the project to enable nullability. No more large PRs like this because after this change you opt in at the file level simply by deleting the #nullable disable at the top of the file. That is a very incremental change that is easier to review
  • Makes it easier to deal with files shared amongst multiple projects because every file is always compiled in the same nullability mode

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
untriaged Request triage from a team member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants