-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Conversation
…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.
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. |
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this makes it a little harder to review, but it otherwise seems positive
#pragma warning disable IDE0240 // Nullable directive is redundant (when file is included to a project that already enables nullable | ||
|
||
#nullable enable | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 See property Possibly, dotnet/sdk may wish to do something similar. |
@RikkiGibson I'm going through and manually resolving all |
…Other minor adjustments based on PR feedback.
Another approach to consider is:
This approach has a couple of advantages:
|
@@ -55,7 +55,7 @@ public CommandResult Execute() | |||
// Reset the Reporters to the new Console Out and Error. | |||
Reporter.Reset(); | |||
|
|||
if (!string.IsNullOrEmpty(_workingDirectory)) | |||
if (_workingDirectory is not null && _workingDirectory.Length != 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
string.IsNullOrEmpty
does not have the proper attributes in .NET Framework to be recognized as a null-check. Therefore, these cause errors since we build for both TFMs. There are several other options to avoid this problem, but are all generally workarounds. On the day I removed all !
uses (called the null-forgiving operator), I also converted all the string.IsNullOrEmpty
to have the functional equivalent check like this (for the projects being converted in this PR).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you considered only running nullable checks when targeting core? Highly recommend in multi targeted projects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have but I'm not doing this as a project. This PR was something I started as a side-thing in March and only revived the PR as there was renewed interest in making the repo nullability compatible. If someone on my team wants to make a decision on the direction of this, then that's fine. All I've done it make an old PR buildable and express the problems around nullability to @marcpopMSFT.
You've suggested some completely reasonable ways to handle this stuff but I don't even know if this is a project we want to get done yet or what the timeframe would be. Only "decision" we made was we didn't want to use the null-forgiving operator (!) when it can be avoided. This repo doesn't have strong, identifiable standards for coding as it is an amalgamation of many different repos.
Type iismType = Type.GetTypeFromCLSID(new Guid(CLSID_InternetSecurityManager)); | ||
internetSecurityManager = (IInternetSecurityManager)Activator.CreateInstance(iismType); | ||
Type? iismType = Type.GetTypeFromCLSID(new Guid(CLSID_InternetSecurityManager)); | ||
if (iismType is not null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should there be an error if it is null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it is null right now, it'll throw a null-reference exception. When discussing doing this work with @marcpopMSFT, he accepted that we would have functional changes when adding null-checks like this. For this situation, if it is null, I'm assuming that zone
check will fail so this method will return false
.
@@ -111,7 +109,7 @@ internal static bool TryGetMostFitRuntimeIdentifier( | |||
|
|||
private DependencyContext CreateDependencyContext() | |||
{ | |||
using (Stream depsFileStream = File.OpenRead(_depsFilePath)) | |||
using (Stream depsFileStream = File.OpenRead(_depsFilePath ?? string.Empty)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like an error would be better
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't opt to adding any additional logic, especially throwing exceptions, as I originally didn't intend on changing ANY logic. However, we decided that we didn't want to use the null-forgiving operator (!), so the only changes I've made in logic is checking for null and letting the program "continue" if it was null, as you saw above in your one comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before this, it would've failed (with a NRE) if _depsFilePath were somehow null, so this actually does change the logic to make it fail in a different way. I think "_depsFilePath is unexpectedly null" is more clear and accurate than "could not find file ''", which might have multiple causes.
I commented on multiple like this where I think the error you end up getting from ?? string.Empty (or equivalent) is less clear and actionable than what the error otherwise would've been. I think there's a lot of potential benefit from nullable (and hence from a PR like this), but I also think it's important to not miss the woods for the trees: bringing an error forward so we can act on it is ideal. Hiding it to make an analyzer happy is not. Though I agree I'm proposing adding more than you did, I think it would make it clearer rather than less clear. Does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, @marcpopMSFT is aware the logic will change in unintended ways, but we have not opted on redesigning the code around nullability, as that is a major project. We're only making the code handle nullability support. If that support results in bugs, that is an understood risk and not in the scope of this PR.
Your concerns are exactly the concerns I brought up with Marc, so I'm on your side for this. But I'm not supposed to invest the effort in making things better, only making them compatible. It would take way too long to write this code properly to handle nullability in the design of it. There are thousands of situations just like this and we don't have a way to make them less error prone without knowing the entire codebase. As it stands, I'm doing the minimum viable changes to make the code compatible with nullability, which would still take a while. It is not intended to make the code better. Changing a potential null-ref error into a different error is completely reasonable for this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The guidance I got from other teams was to make the switch over first and then start investing in changing hte logic. They did indicate that changing the logic to provide better error handling and messaging was a desired outcome but this switch is already going to be quite challenging. I wonder if it makes sense to create an issue with a list of all the spots we want to revisit later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if it is possible to create that list as it would be a majority of the changes. Most uses of string
in the codebase fall into this category, and there are likely thousands of strings.
{ | ||
var installType = GetWorkloadInstallType(sdkFeatureBand, dotnetDir); | ||
var architecture = RuntimeInformation.ProcessArchitecture.ToString(); | ||
|
||
if (installType == InstallType.FileBased) | ||
if (dotnetDir is not null && installType == InstallType.FileBased) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think dotnetDir is explicitly non-null here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the call stack, the SdkDirectoryWorkloadManifestProvider
has the _sdkOrUserLocalPath
field which is nullable. So that gets passed into this as dotnetDir
. Unless you change the design of all of these classes to be string
vs string?
, then you can't really undo this stuff.
|
||
namespace Microsoft.NET.Sdk.WorkloadManifestReader | ||
{ | ||
public record WorkloadRootPath(string? Path, bool Installable); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this makes it a little harder to review, but it otherwise seems positive
|
||
if (preferredPackageId.Length != 0) | ||
if (preferredPackageId is not null && preferredPackageId.Length != 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can do this?
if (preferredPackageId is not null && preferredPackageId.Length != 0) | |
if (preferredPackageId?.Length != 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you change it to that, that doesn't guarantee that preferredPackageId
is non-null. If it is null, it won't equal 0, and therefore, you couldn't use it in the if-statement, since it is used as an index. You need to check it is non-null explicitly before it is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. How about preferredPackageId?.Length > 0? I don't think null > 0
# Conflicts: # src/WebSdk/Publish/Tasks/Tasks/MsDeploy/CreateMSDeployScript.cs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the scope of this change, I think this looks good to me. The only change I'd still like to see is the WorkloadSdkResolver change to make it never return null. Thanks for making all these changes! I can see them helping us avoid nullability errors going forward 🙂
@jaredpar We discussed it. What we're going to do is:
@Forgind I'll adjust the |
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: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.#nullable enable
in the file, or<Nullable>enable</Nullable>
null
or you need to add custom logic to handle thenull
situationnull
should be handled?
to everything is really detrimental as you can cause more work for yourself than just handlingnull
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.!
entirely. In this PR, I've converted several uses ofstring.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 tostring.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 ifstring.Empty
is an acceptable value?
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>
Other changes
record
to replace passing around a tuple#nullable enable
removed as all projects they were shared with became nullable-enabled projectsGoing forward
After some discussion, it seems like the forward strategy will be:
#nullable enable
at the top of every shared source file (follow-up PR)<Nullable>enable</Nullable>
to them<Nullable>enable</Nullable>
to the main Directory.Build.props, remove it from every .csproj, and remove#nullable enable
from the shared source files