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 9 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.
  • 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

…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 1228 to 1238
//Microsoft.Web.Deployment.DeploymentSyncParameterValidationKind validationKind = Microsoft.Web.Deployment.DeploymentSyncParameterValidationKind.None;
//Microsoft.Web.Deployment.DeploymentSyncParameterValidationKind currentvalidationKind;
//Microsoft.Web.Deployment.DeploymentSyncParameterValidationKind currentValidationKind;
//string[] validationKinds = item.Kind.Split(new char[] { ',' });

//foreach (string strValidationKind in validationKinds)
//{
// if (System.Enum.TryParse<Microsoft.Web.Deployment.DeploymentSyncParameterValidationKind>(strValidationKind, out currentvalidationKind))
// if (System.Enum.TryParse<Microsoft.Web.Deployment.DeploymentSyncParameterValidationKind>(strValidationKind, out currentValidationKind))
// {
// validationKind |= currentvalidationKind;
// validationKind |= currentValidationKind;
// }
//}
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 have no idea why this is here. A relic of the past? 🗿

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
Copy link
Member

Choose a reason for hiding this comment

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

BOM change?

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'd assume so. I had changed more in this file initially but removed those changes.

Copy link
Member

Choose a reason for hiding this comment

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

Consider reverting all changes in this file then.

@@ -9,6 +9,7 @@
<RepositoryType>git</RepositoryType>
<DefineConstants Condition="'$(IncludeAspNetCoreRuntime)' == 'false'">$(DefineConstants);EXCLUDE_ASPNETCORE</DefineConstants>
<IsPackable>true</IsPackable>
<Nullable>enable</Nullable>
Copy link
Member

Choose a reason for hiding this comment

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

Should this be in a directory.build.props perhaps?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not possible without doing the entire repo first or figuring out some way of untangling the mess of shared code files. Or, you manually check nested project directories to see if all the projects in that directory already use nullability and then added a new Directory.Build.props for that sub-directory. Doesn't seem worthwhile, though. This is way too initial to do that kind of change.

@@ -317,7 +317,7 @@ static ICommand CreateCommandFromRunProperties(ProjectInstance project, RunPrope
CommandSpec commandSpec = new(runProperties.RunCommand, runProperties.RunArguments);

var command = CommandFactoryUsingResolver.Create(commandSpec)
.WorkingDirectory(runProperties.RunWorkingDirectory);
.WorkingDirectory(runProperties.RunWorkingDirectory ?? string.Empty);
Copy link
Member

Choose a reason for hiding this comment

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

could this previously be null and any concerns about being empty now. Assuming we didn't have explicit null vs. nullorwhitespace checks

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 was already defined as string? RunWorkingDirectory at the top of this file. What changed was WorkingDirectory now became part of a nullable-enabled assembly, and its parameter is defined as string projectDirectory. That means, previously, if this was passed null, it would assign it to process.StartInfo.WorkingDirectory. If the decompiled code is to be believed, the default for that property is "", so I just left it as that in this situation. The code for it does handle null, and it'll just return string.Empty anyway.
image


//only Microsoft.DotNet.MSBuildSdkResolver (net7.0) has nullables enabled
#pragma warning disable IDE0240 // Remove redundant nullable directive
#pragma warning disable IDE0240
#nullable enable
Copy link
Member

Choose a reason for hiding this comment

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

shouldn't this just remove the #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.

You cannot. It is a shared file. You'd have to do the entire Microsoft.NET.Build.Tasks and (likely) the dotnet CLI project, which are thousands of changes.

@@ -1,10 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#pragma warning disable IDE0240 // Nullable directive is redundant (when file is included to a project that already enables nullable

#pragma warning disable IDE0240
#nullable enable
Copy link
Member

Choose a reason for hiding this comment

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

ditto, should we remove the #nullable enable an

Copy link
Member Author

Choose a reason for hiding this comment

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

If you see changes like this, it is because I initially tried to remove it, but then added it back again because it is a shared source file and cannot be removed.


if (line.Length == 0 || line[0] == '#')
if (line is null || line.Length == 0 || line[0] == '#')
Copy link
Member

Choose a reason for hiding this comment

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

should this instead be string.IsNullOrWhitespace(line)?

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 think I did that at first but changed it to this because string.IsNullOrWhitespace is not recognized by the analyzer as a null-check. Therefore, the call to the indexer in the rest of the or-statement would need to be line![0] == '#' which is a little jankier. This seemed like a simpler change since it is more explicit to the analyzer.

{
if (sourcePath == null)
Copy link
Member

Choose a reason for hiding this comment

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

are empty paths valid here?

Copy link
Member Author

Choose a reason for hiding this comment

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

So, the property that eventually flows to here could return null (from ConflictItem.cs):
image

The value returned from this method ends up here, and you'll see that someone clearly didn't mind if it was null via the comment (also from ConflictItem.cs):
image

@@ -57,7 +57,7 @@ public CommandResult Execute()

if (!string.IsNullOrEmpty(_workingDirectory))
{
_environment.SetWorkingDirectory(_workingDirectory);
_environment.SetWorkingDirectory(_workingDirectory!);
Copy link
Member Author

Choose a reason for hiding this comment

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

! Justification: string.IsNullOrEmpty check

{
correctedPath = string.Empty;
if (string.IsNullOrWhiteSpace(existingPath))
{
return false;
}

IEnumerable<string> paths = existingPath.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
IEnumerable<string> paths = existingPath!.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
Copy link
Member Author

Choose a reason for hiding this comment

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

! Justification: string.IsNullOrEmpty check

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.

3 participants