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

Annotate most of Microsoft.VisualStudio.ProjectSystem.Managed #5003

Merged
merged 14 commits into from Jul 11, 2019

Conversation

drewnoakes
Copy link
Member

No description provided.

@drewnoakes drewnoakes requested a review from a team as a code owner July 9, 2019 03:16
@@ -41,6 +40,7 @@ internal static class ImmutableCollectionLinqExtensions
return false;
}

[return: MaybeNull]
public static T FirstOrDefault<T, TArg>(this ImmutableArray<T> immutableArray, Func<T, TArg, bool> predicate, TArg arg)
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 the recommended approach for indicating that an output with unconstrained generic type can be null.

Copy link
Member

Choose a reason for hiding this comment

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

Ugh - that is not great.

@@ -49,7 +49,7 @@ internal static class ImmutableCollectionLinqExtensions
return obj;
}

return default;
return default!;
Copy link
Member Author

Choose a reason for hiding this comment

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

Unfortunately the attribute above doesn't influence the member body, so we need to ! force this here. Tracked by dotnet/roslyn#36039.

private IWorkspaceProjectContext _context;
private ExportLifetimeContext<IWorkspaceContextHandler>[] _handlers;
private IWorkspaceProjectContext? _context;
private ExportLifetimeContext<IWorkspaceContextHandler>[] _handlers = Array.Empty<ExportLifetimeContext<IWorkspaceContextHandler>>();
Copy link
Member Author

Choose a reason for hiding this comment

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

In some cases it is easier to use singleton empty collections rather than null. Doing so can avoid lots of null checks or !, especially when there's an implicit relationship between being initialised and non-null (as happens a lot in this repo).

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Copy link
Member

Choose a reason for hiding this comment

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

I agree in some cases, but in this case it would be a bug that this is null that is no longer caught.

}
}

private void ProcessProjectBuildFailure(IProjectRuleSnapshot snapshot)
{
Assumes.NotNull(_context);
Copy link
Member Author

Choose a reason for hiding this comment

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

Unfortunately these guard methods are not annotated to indicate that the argument is not null after this point. There is a DoesNotReturnIf(bool) attribute, but not a DoesNotReturnIfNull(string parameterName) attribute. It seems we have to ! the first usage after this point. I filed dotnet/roslyn#37071 to see if we could just ! the argument to the guard method directly, which would be nicer.

Copy link
Contributor

Choose a reason for hiding this comment

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

can we file an issue on the VSSDK folks for this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Can do, but there's nothing they can do until the compiler supports it. As soon as there's compiler support, I'll send them a PR if they don't get to it first.

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually it's possible to do this with the current attributes:

void AssertNotNull([NotNull] object? o) { }

string M(object? o)
{
    AssertNotNull(o);
    return o.ToString(); // no diagnostic here
}

namespace Microsoft.VisualStudio.ProjectSystem.LanguageServices
{
internal class BuildOptions
{
private static readonly PropertyInfo s_analyzerConfigPathsProperty = typeof(CommandLineArguments).GetProperty("AnalyzerConfigPaths");
private static readonly PropertyInfo? s_analyzerConfigPathsProperty = typeof(CommandLineArguments).GetProperty("AnalyzerConfigPaths");
Copy link
Member Author

Choose a reason for hiding this comment

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

Type.GetProperty is not annotated. I saw a later null test and annotated this. If the ? here was removed, it would still compile without warning.

I mention this as there are probably other places like this in the repo.

@@ -49,7 +46,7 @@ public Task OnProjectFactoryCompleted()
}
else
{
_projectLoadedInHost.TrySetResult(false);
_projectLoadedInHost.TrySetResult(null);
Copy link
Member Author

Choose a reason for hiding this comment

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

We were boxing a bool as an object here.

@@ -69,7 +67,7 @@ internal Enumerator(in LazyStringSplit split)
_index = 0;
_input = split._input;
_delimiter = split._delimiter;
Current = null;
Current = null!;
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 this makes sense for enumerators. Technically Current can be null, but 99.99% of consumers are foreach, in which case you won't see that state of the enumerator. You can still see nulls if the sequence has null elements though (which it won't here).

@@ -75,7 +75,7 @@ void HandleChangesForRule(bool resolved, IProjectChangeDescription projectChange
foreach (string removedItem in projectChange.Difference.RemovedItems)
{
string dependencyId = resolved
? projectChange.Before.GetProjectItemProperties(removedItem).GetStringProperty(ResolvedAssemblyReference.OriginalItemSpecProperty) ?? removedItem
? projectChange.Before.GetProjectItemProperties(removedItem)!.GetStringProperty(ResolvedAssemblyReference.OriginalItemSpecProperty) ?? removedItem
Copy link
Member Author

Choose a reason for hiding this comment

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

Assuming the changed item has properties here, hence !.


string originalItemSpec = resolved
? properties.GetStringProperty(ResolvedAssemblyReference.OriginalItemSpecProperty)
? properties.GetStringProperty(ResolvedAssemblyReference.OriginalItemSpecProperty) ?? itemSpec
Copy link
Member Author

Choose a reason for hiding this comment

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

Behaviour change. if somehow the original item spec property was null, use the item spec.

Copy link
Member

Choose a reason for hiding this comment

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

We should probably change these methods to return empty to behave identiical to MSBuild; if a property doesn't exist its "empty". Probably need to rationalize these extension methods with this ProjectRuleSnapshotExtensions.

@@ -37,7 +36,7 @@ internal static class MetadataExtensions
/// <param name="key">The key that identifies the property to look up.</param>
/// <param name="stringValue">The value of the string if found and non-empty, otherwise <see langword="null"/>.</param>
/// <returns><see langword="true"/> if the property was found with a non-empty value, otherwise <see langword="false"/>.</returns>
public static bool TryGetStringProperty(this IImmutableDictionary<string, string> properties, string key, out string stringValue)
public static bool TryGetStringProperty(this IImmutableDictionary<string, string> properties, string key, [NotNullWhen(returnValue: true)] out string? stringValue)
Copy link
Member Author

Choose a reason for hiding this comment

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

TryGet* pattern.

@davkean
Copy link
Member

davkean commented Jul 11, 2019

We're want to merge this in quickly to avoid it fall behind. @jmarolf Are you good with the change?

@jmarolf
Copy link
Contributor

jmarolf commented Jul 11, 2019

Yep, lets not hold this one up

@drewnoakes drewnoakes merged commit 80d2b6e into dotnet:master Jul 11, 2019
@drewnoakes drewnoakes deleted the annotations branch July 11, 2019 23:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants