-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Remove nullable disable warnings #48846
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
Conversation
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.
Pull Request Overview
This pull request removes bulk usages of "#nullable disable warnings" to enforce full nullable validation across the codebase. In doing so, it updates type annotations and parameters in multiple files to adapt to stricter nullability rules.
- Removed "#nullable disable warnings" directives from various files.
- Adjusted method and record parameter types to non-nullable where appropriate.
- Added a null-coalescing fallback to ensure string values are not unexpectedly null.
Reviewed Changes
Copilot reviewed 44 out of 44 changed files in this pull request and generated 1 comment.
Show a summary per file
File | Description |
---|---|
src/Cli/dotnet/Commands/Sdk/Check/SdkOutputWriter.cs | Removed "#nullable disable warnings". |
src/Cli/dotnet/Commands/Sdk/Check/RuntimeOutputWriter.cs | Removed "#nullable disable warnings". |
src/Cli/dotnet/Commands/Run/RunProperties.cs | Updated RunProperties record to enforce a non-null RunCommand. |
src/Cli/dotnet/Commands/Run/RunCommand.cs | Changed restoreArgs parameter to be non-nullable. |
src/Cli/dotnet/Commands/Run/LaunchSettings/* | Removed "#nullable disable warnings". |
src/Cli/dotnet/Commands/Run/CommonRunHelpers.cs | Adjusted parameters from nullable to non-nullable for consistency. |
src/Cli/dotnet/Commands/New/PostActions/* | Removed "#nullable disable warnings". |
src/Cli/dotnet/Commands/New/NewCommandParser.cs | Added a fallback for NetStandardImplicitPackageVersion. |
src/Cli/dotnet/Commands/New/MSBuildEvaluation/* | Removed "#nullable disable warnings" and updated dictionary types. |
src/Cli/dotnet/Commands/New/DotnetCommandCallbacks.cs | Removed "#nullable disable warnings". |
src/Cli/dotnet/CommandFactory/CommandSpec.cs | Updated parameter types and default values for clarity. |
Comments suppressed due to low confidence (5)
src/Cli/dotnet/CommandFactory/CommandSpec.cs:8
- Updating the path parameter to be non-nullable assumes that all initializations of CommandSpec provide a valid path. Verify that this contract is maintained across the codebase to prevent unexpected null values.
public class CommandSpec( string path, string? args, Dictionary<string, string>? environmentVariables = null)
src/Cli/dotnet/Commands/Run/RunCommand.cs:324
- Converting the restoreArgs parameter from nullable to non-nullable requires confirming that all call sites pass a valid array. Verify that this change aligns with caller expectations and handle potential null values upstream if needed.
static ProjectInstance EvaluateProject(string? projectFilePath, Func<ProjectCollection, ProjectInstance>? projectFactory, string[] restoreArgs, ILogger? binaryLogger)
src/Cli/dotnet/Commands/Run/CommonRunHelpers.cs:37
- Updating the args parameter to be non-nullable enforces a stricter contract. Ensure that all callers are updated to provide a non-null string array.
public static Dictionary<string, string> GetGlobalPropertiesFromArgs(string[] args)
src/Cli/dotnet/Commands/New/NewCommandParser.cs:171
- [nitpick] Using an empty string as a fallback for NetStandardImplicitPackageVersion is acceptable if it conforms with downstream expectations. Confirm that this fallback value appropriately handles cases where the version string might be null.
{ "NetStandardImplicitPackageVersion", new FrameworkDependencyFile().GetNetStandardLibraryVersion() ?? "" },
src/Cli/dotnet/Commands/New/MSBuildEvaluation/MSBuildEvaluator.cs:198
- Allowing null values in the properties dictionary may lead to downstream issues if consumers assume non-null strings. Review the consumers of this dictionary to ensure proper null handling or consider defaulting to empty strings where appropriate.
Dictionary<string, string?> properties = new()
{ | ||
public string Path { get; } = path; | ||
|
||
public string Args { get; } = args; | ||
public string? Args { get; } = args; |
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.
Having Args
be potentially null
seems suspicious, I considered changing this to the following but don't know enough about the code base to say if that is okay
public string Args { get; } = args ?? "";
@@ -170,7 +168,7 @@ private static CliTemplateEngineHost CreateHost( | |||
{ "prefs:language", preferredLang }, | |||
{ "dotnet-cli-version", Product.Version }, | |||
{ "RuntimeFrameworkVersion", new Muxer().SharedFxVersion }, | |||
{ "NetStandardImplicitPackageVersion", new FrameworkDependencyFile().GetNetStandardLibraryVersion() }, | |||
{ "NetStandardImplicitPackageVersion", new FrameworkDependencyFile().GetNetStandardLibraryVersion() ?? "" }, |
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.
@baronfel should verify this change as being accurate. It is also possible the templating engine is wrong and it should accept a Dictionary<string, string?>
but I don't have the depth to say which is 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.
The GetNetStandardLibraryVersion
method shouldn't return a nullable - it's one of that hard-coded, required things and we should blow up if it's not present.
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.
Happy to change that to throw on failure if you think it's the best course.
using Microsoft.Build.Execution; | ||
using Microsoft.DotNet.Cli.Utils; | ||
|
||
namespace Microsoft.DotNet.Cli.Commands.Run; | ||
|
||
internal record RunProperties(string? RunCommand, string? RunArguments, string? RunWorkingDirectory) | ||
internal record RunProperties(string RunCommand, string? RunArguments, string? RunWorkingDirectory) |
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 is another place where having arguments, specifically RunArguments
, be potentially null
feels potentially off and ""
may work.
@@ -39,7 +38,7 @@ private static bool IsSolutionFolderPathInDirectoryScope(string relativePath) | |||
|
|||
public SolutionAddCommand(ParseResult parseResult) : base(parseResult) | |||
{ | |||
_fileOrDirectory = parseResult.GetValue(SolutionCommandParser.SlnArgument); | |||
_fileOrDirectory = parseResult.GetValue(SolutionCommandParser.SlnArgument)!; |
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 ParseResult
type in general does not work well with nullable analysis because GetValue
is hard coded to return T?
. One possible solution is add a GetValueOrThrow
extension method. Happy to make that change if it's desired.
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.
We chatted about this briefly but the main gap here is that the S.CL library enforces the arity of an option/argument but there's no way for that Arity to influence the return type of this method.
var projectPlatform = projectInstancePlatforms.FirstOrDefault( | ||
platform => platform.Replace(" ", string.Empty) == solutionPlatform.Replace(" ", string.Empty), projectInstancePlatforms.FirstOrDefault()); | ||
platform => platform.Replace(" ", string.Empty) == solutionPlatform.Replace(" ", string.Empty), projectInstancePlatforms.FirstOrDefault()!); |
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.
Lacking the !
on the default value (last argument) the compiler will end up inferring FirstOrDefault<string?>
not FirstOrDefault<string>
. That is because the default value directly infers the value for T
and FirstOrDefault
is null
returning. Don't know enough about the semantics here to suggest a more null friendly pattern.
@@ -5,4 +5,4 @@ namespace Microsoft.DotNet.Cli.Commands.Test.IPC.Models; | |||
|
|||
internal sealed record FileArtifactMessage(string? FullPath, string? DisplayName, string? Description, string? TestUid, string? TestDisplayName, string? SessionUid); | |||
|
|||
internal sealed record FileArtifactMessages(string? ExecutionId, string InstanceId, FileArtifactMessage[] FileArtifacts) : IRequest; | |||
internal sealed record FileArtifactMessages(string? ExecutionId, string? InstanceId, FileArtifactMessage[] FileArtifacts) : IRequest; |
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.
It seems like most record
types in the sdk default to putting ?
on all parameters. I suspect that many could, and should, be unannotated.
@@ -114,7 +115,7 @@ internal sealed record TestModule(RunProperties RunProperties, string? ProjectFu | |||
|
|||
internal sealed record Handshake(Dictionary<byte, string>? Properties); | |||
|
|||
internal sealed record CommandLineOption(string? Name, string? Description, bool? IsHidden, bool? IsBuiltIn); | |||
internal sealed record CommandLineOption(string Name, string Description, bool? IsHidden, bool? IsBuiltIn); |
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.
Same comment about record
declarations
@@ -27,7 +25,7 @@ internal static FlatException[] Flatten(string? errorMessage, Exception? excepti | |||
IEnumerable<Exception?> aggregateExceptions = exception switch | |||
{ | |||
AggregateException aggregate => aggregate.Flatten().InnerExceptions, | |||
_ => [exception?.InnerException], | |||
_ => [exception?.InnerException!], |
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 is known issue in collection expression null inference that I needed to work around. @cston
@@ -29,13 +28,13 @@ internal static class TestCommandParser | |||
{ | |||
Description = CliCommandStrings.CmdTestCaseFilterDescription, | |||
HelpName = CliCommandStrings.CmdTestCaseFilterExpression | |||
}.ForwardAsSingle(o => $"-property:VSTestTestCaseFilter={SurroundWithDoubleQuotes(o)}"); | |||
}.ForwardAsSingle(o => $"-property:VSTestTestCaseFilter={SurroundWithDoubleQuotes(o!)}"); |
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.
Most of the !
is related to the ParseResult
issue mentioned above.
project.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.Platform, "*", solutionPlatform, projectPlatform)); | ||
} | ||
|
||
foreach (var solutionBuildType in solution.BuildTypes) | ||
{ | ||
var projectBuildType = projectInstanceBuildTypes.FirstOrDefault( | ||
buildType => buildType.Replace(" ", string.Empty) == solutionBuildType.Replace(" ", string.Empty), projectInstanceBuildTypes.FirstOrDefault()); | ||
buildType => buildType.Replace(" ", string.Empty) == solutionBuildType.Replace(" ", string.Empty), projectInstanceBuildTypes.FirstOrDefault()!); |
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.
Same FirstOrDefault
pattern as above.
if (nugetPackageDownloader is not null) | ||
{ | ||
IsPackageDownloaderProvided = true; | ||
PackageDownloader = nugetPackageDownloader; | ||
} | ||
else | ||
{ | ||
IsPackageDownloaderProvided = false; | ||
PackageDownloader = new NuGetPackageDownloader.NuGetPackageDownloader( | ||
TempPackagesDirectory, | ||
filePermissionSetter: null, | ||
new FirstPartyNuGetPackageSigningVerifier(), | ||
nugetLogger, | ||
Reporter, | ||
restoreActionConfig: RestoreActionConfiguration, | ||
verifySignatures: VerifySignatures, | ||
shouldUsePackageSourceMapping: true); | ||
} |
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 is just a simple expansion of the original code that is more NRT friendly. Could also change this to be a tuple return but this seemed more readable.
key.Equals("DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR", StringComparison.InvariantCultureIgnoreCase) | ||
? null | ||
: Environment.GetEnvironmentVariable(key)); | ||
var ridFileName = "NETCoreSdkRuntimeIdentifierChain.txt"; | ||
var sdkPath = dotnetRootPath is not null ? Path.Combine(dotnetRootPath, "sdk") : "sdk"; |
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 is a case where the NRT analysis seems to have spotted a legitimate bug in the code. In the case that dotnetRootpath
is null
then the Path.Combine
calls below will throw. I changed this to just use "sdk"
as a relative path but possible that a throws
here is a better idea.
@@ -1,7 +1,7 @@ | |||
// Licensed to the .NET Foundation under one or more agreements. | |||
// The .NET Foundation licenses this file to you under the MIT license. | |||
|
|||
#nullable disable warnings | |||
#nullable disable |
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.
For most of the build tasks there was a single ?
annotation. Instead of fully annotating the build tasks, which is hundreds of changes, I just removed the errant annotation and disable NRT in the file. Seems much better to approach those as a separate change where a good deal of thought is put into how NRT should be structured in tasks.
@marcpopMSFT, @MiYanni, @baronfel This is a follow up change where I removed all uses of |
.AddCompletions(CliCompletion.RunTimesFromProjectFile); | ||
|
||
public static Option<string> LongFormRuntimeOption = | ||
new DynamicForwardedOption<string>("--runtime") | ||
{ | ||
HelpName = RuntimeArgName | ||
}.ForwardAsMany(RuntimeArgFunc) | ||
}.ForwardAsMany(RuntimeArgFunc!) |
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 did you need the !
for these? The static function is known, isn't it?
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.
public static Option<T> ForwardAsMany<T>(this ForwardedOption<T> option, Func<T?, IEnumerable<string>> format);
This requires that the delegate have a string?
parameter, presumably more fallout of ParseResult
design. The RuntimeArgFunc
though depends on having a non-null parameter. The parameter is immediately passed to GetArchFromRid
and that throws on null
.
I believe that if you do not have a dotnet.config in your current directory (or a parent/grandparent/etc. directory), this change means that you cannot use this version of the SDK with debug bits because of a new Debug.Assert. |
Process terminated. |
This change removes all uses of
#nullable disable warnings
in the code base. This means the files now get full nullable validation where previously they were using annotations with no compiler enforcement. For the most part this change just follows the warnings that result from removing the directive but there are a few small checks that got added, likeDebug.Assert
, that are worth looking at.I will also call out in the review a few patterns in the sdk / System.CommandLine that are hard to work with in nullable.
Note: to make this easier to review I have added review comments any place I made a change that was not purely mechanical in nature.