Skip to content

Latest commit

 

History

History
221 lines (162 loc) · 11.1 KB

README.md

File metadata and controls

221 lines (162 loc) · 11.1 KB

⚠️ End-of-life note: C# 8+ (VS 2019+) supports nullable reference types which supersedes - and perfectly aligns with - Implicit Nullability. Therefore ReSharper/Rider 2020.1 is the last version supported by Implicit Nullability.

Implicit Nullability ReSharper Extension

Build status Gitter Line Coverage Branch Coverage

ReSharper Gallery Page

History of changes

Idea

The basic idea of this extension is to change the behavior of ReSharper's static nullability analysis so that specific code elements get a default nullability annotation without specifying an explicit [NotNull] or [CanBeNull] attribute. For example, reference types in method parameters are by default [NotNull] (→ they need an explicit [CanBeNull] to become nullable).

Code Sample

With enabled Implicit Nullability for specific, configurable, syntax elements, the following rules apply.

  • Reference types are by default implicitly [NotNull].
  • Their nullability can be overridden with an explicit [CanBeNull] attribute.
  • Optional method parameters with a null default value are implicitly [CanBeNull].

In a nutshell, the code showed in the picture above ...

public string Bar(string a, [CanBeNull] string b, string c = null)
{
    // ...
}

... implicitly becomes ...

[NotNull]
public string Bar([NotNull] string a, [CanBeNull] string b, [CanBeNull] string c = null)
{
    // ...
}

[NotNull] as default

Without this extension the default nullability value is "unknown" which means that ReSharper excludes these elements for its nullability analysis. As a result of the changed default nullability of this extension we have to place [CanBeNull] annotations only for specific code elements (e.g. a reference type parameter where it should be allowed to pass null as argument) and don't need explicit annotations for the majority of cases (in code bases which try to reduce passing null references to a minimum).

Async method return types (requires ReSharper 9.2)

ReSharper 9.2 introduced support for nullability analysis for async (Task<T> return typed) methods. With enabled Implicit Nullability for method return values, Task<T> return typed methods also become implicitly [ItemNotNull] (ReSharper uses this attribute to refer to the value of Task<T>). For nullable Task<T> return values, this can be overridden with [ItemCanBeNull].

Improves static analysis quality

The following example program contains a potential NullReferenceException in command​.Equals​("Hello") because the programmer missed that GetCommand() could also return null.

public static void Main(string[] args)
{
    string command = GetCommand(args);

    if (command.Equals("Hello"))
        Console.WriteLine("Hello World!");
}

private static string GetCommand(string[] args)
{
    if (args.Length < 1)
        return null;

    return args[0];
}

With enabled Implicit Nullability this bug would have been detected by ReSharper's static analysis.

  1. ReSharper would warn about returning null in GetCommand() because this method would be implicitly annotated as [NotNull].
  2. This warning would be solved by the programmer by adding [CanBeNull] to GetCommand().
  3. As a consequence of the [CanBeNull] attribute, ReSharper would now warn about the potential NullReferenceException in the command​.Equals​("Hello") call in Main().

Improves documentation

In the example above Implicit Nullability forces the programmer to fix the missing [CanBeNull] attribute on GetCommand(). This shows how the number of [CanBeNull] annotations will be increased in the code base and therefore doesn't only improve ReSharper's static analysis but also the documentation of method signatures (contracts).

Fody NullGuard

Another goal of this extension is to bring ReSharper's static analysis in sync with the implicit null checks of Fody NullGuard. For example, this Fody weaver injects throw new ArgumentNullException​(/*...*/) statements for method parameters into method bodies using the same rules as Implicit Nullability. In other words this weaver adds runtime checks for nullability to ReSharper's static analysis.

C# Nullable reference types

C# 8 will get Nullable reference types which brings option types into the C# language. This matches the semantics of Implicit Nullability and goes even further because it extends it to locals, generic parameters, ... because the nullability annotation works on type level.

[CanBeNull]
string Bar([CanBeNull] string a, string b)
{
    string result = a;
    return result;
}

... then corresponds to ...

string? Bar(string? a, string b)
{
    string? result = a;
    return result;
}

This will make Implicit Nullability unnecessary in the future and at the same time a perfect "upgrade path" because a good [CanBeNull]-annotated code base will be easily portable to C# 8.

Differences to ReSharper's "Implicit [NotNull] Value Analysis Mode"

Since version 2016.1 ReSharper supports (in the "internal mode") a comparable feature (see Code Inspection | Settings) with the following differences.

Implicit Nullability ...

  • ... rules can be enabled / disabled for specific code elements like parameters or (readonly) fields.
  • ... supports (project specific) configuration by code.
  • ... adds additional warnings.
  • ... adds type highlighting for explicit or implicit [NotNull] elements.

Type highlighting

Explicit or implicit [NotNull] element types are highlighted with a dotted underline. (See the pink underlines in the Bar-method in the sample screenshot above.) This helps to recognize all [NotNull] elements, especially inferred [NotNull] elements from a base class and code elements which are configured as implicitly [NotNull].

The highlighting can be enabled/disabled on the Implicit Nullability options page, and the colors can be configured in Visual Studio's "Fonts and Colors" options.

Configuration

Implicit nullability can be enabled or disabled for specific syntax elements in the Code Inspection | Implicit Nullability options page.

Options Page

Code configuration

Implicit Nullability can also be configured by code using an AssemblyMetadataAttribute. This has the advantage that the configuration gets compiled into the assembly so that consumers of the assembly with installed Implicit Nullability get the same implicit nullability annotations of the compiled code elements as within the library's solution.

Example:

[assembly: AssemblyMetadata("ImplicitNullability.AppliesTo",
                            "InputParameters, RefParameters, OutParametersAndResult, Fields, Properties")]
[assembly: AssemblyMetadata("ImplicitNullability.Fields", "RestrictToReadonly")]
[assembly: AssemblyMetadata("ImplicitNullability.Properties", "RestrictToGetterOnly")]
[assembly: AssemblyMetadata("ImplicitNullability.GeneratedCode", "Exclude")]

⚠️ After changing the settings (either by code or in the options page), cleaning the solution cache is necessary to update already analyzed code.

Code inspection warnings

In addition to the behavior change of the nullability analysis the following code inspection warnings are provided by this extension.

For more information about these warnings (and their motivation) see their description in the ReSharper Code Inspection | Inspection Severity options page.

"Implicit NotNull conflicts with nullability in base type"

Id: ImplicitNotNullConflictInHierarchy

abstract class Base
{
    public abstract void Method([CanBeNull] string a);
}

class Derived : Base
{
    // "Implicit NotNull conflicts with nullability in base type":
    public override void Method(string a)
    {
    }
}

"Implicit NotNull element cannot override CanBeNull in base type, nullability should be explicit"

Id: ImplicitNotNullElementCannotOverrideCanBeNull

abstract class Base
{
    [CanBeNull]
    public abstract string Method();
}

class Derived : Base
{
    // "Implicit NotNull element cannot override CanBeNull in base type, nullability should be explicit":
    public override string Method() => "";
}

"Implicit NotNull overrides unknown nullability of base member, nullability should be explicit" resp. hint "Implicit NotNull result or out parameter overrides unknown nullability of base member, nullability should be explicit"

Ids: ImplicitNotNullOverridesUnknownBaseMemberNullability resp. ImplicitNotNullResultOverridesUnknownBaseMemberNullability

class Derived : External.Class /* (external code with unannotated 'Method' and 'Function') */
{
    // "Implicit NotNull overrides unknown nullability of base member, nullability should be explicit":
    public override void Method(string a)
    {
    }

    // "Implicit NotNull result or out parameter overrides unknown nullability of base member,
    // nullability should be explicit":
    public override string Function() => "";
}

"Implicit CanBeNull element has an explicit NotNull annotation"

Id: NotNullOnImplicitCanBeNull

// "Implicit CanBeNull element has an explicit NotNull annotation":
void Foo([NotNull] int? a)
{
}

Credits

Big thanks to Fabian Schmied for supporting the design and conception of Implicit Nullability.