Browse Source

Added nullable attributes and use them instead of `!`.

pull/4182/head
Steven Kirk 6 years ago
parent
commit
de6553a0dc
  1. 44
      src/Avalonia.Base/Data/BindingValue.cs
  2. 26
      src/Avalonia.Base/Data/Optional.cs
  3. 4
      src/Avalonia.Base/DirectPropertyBase.cs
  4. 140
      src/Avalonia.Base/Metadata/NullableAttributes.cs
  5. 11
      src/Avalonia.Base/PropertyStore/LocalValueEntry.cs
  6. 2
      src/Avalonia.Base/ValueStore.cs

44
src/Avalonia.Base/Data/BindingValue.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Utilities; using Avalonia.Utilities;
#nullable enable #nullable enable
@ -81,14 +82,14 @@ namespace Avalonia.Data
/// </remarks> /// </remarks>
public readonly struct BindingValue<T> public readonly struct BindingValue<T>
{ {
private readonly T _value; [AllowNull] private readonly T _value;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BindingValue{T}"/> struct with a type of /// Initializes a new instance of the <see cref="BindingValue{T}"/> struct with a type of
/// <see cref="BindingValueType.Value"/> /// <see cref="BindingValueType.Value"/>
/// </summary> /// </summary>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
public BindingValue(T value) public BindingValue([AllowNull] T value)
{ {
ValidateValue(value); ValidateValue(value);
_value = value; _value = value;
@ -96,7 +97,7 @@ namespace Avalonia.Data
Error = null; Error = null;
} }
private BindingValue(BindingValueType type, T value, Exception? error) private BindingValue(BindingValueType type, [AllowNull] T value, Exception? error)
{ {
_value = value; _value = value;
Type = type; Type = type;
@ -154,7 +155,7 @@ namespace Avalonia.Data
BindingValueType.UnsetValue => AvaloniaProperty.UnsetValue, BindingValueType.UnsetValue => AvaloniaProperty.UnsetValue,
BindingValueType.DoNothing => BindingOperations.DoNothing, BindingValueType.DoNothing => BindingOperations.DoNothing,
BindingValueType.Value => _value, BindingValueType.Value => _value,
BindingValueType.BindingError => BindingValueType.BindingError =>
new BindingNotification(Error, BindingErrorType.Error), new BindingNotification(Error, BindingErrorType.Error),
BindingValueType.BindingErrorWithFallback => BindingValueType.BindingErrorWithFallback =>
new BindingNotification(Error, BindingErrorType.Error, Value), new BindingNotification(Error, BindingErrorType.Error, Value),
@ -175,7 +176,7 @@ namespace Avalonia.Data
/// The binding type is <see cref="BindingValueType.UnsetValue"/> or /// The binding type is <see cref="BindingValueType.UnsetValue"/> or
/// <see cref="BindingValueType.DoNothing"/>. /// <see cref="BindingValueType.DoNothing"/>.
/// </exception> /// </exception>
public BindingValue<T> WithValue(T value) public BindingValue<T> WithValue([AllowNull] T value)
{ {
if (Type == BindingValueType.DoNothing) if (Type == BindingValueType.DoNothing)
{ {
@ -190,7 +191,8 @@ namespace Avalonia.Data
/// Gets the value of the binding value if present, otherwise the default value. /// Gets the value of the binding value if present, otherwise the default value.
/// </summary> /// </summary>
/// <returns>The value.</returns> /// <returns>The value.</returns>
public T GetValueOrDefault() => HasValue ? _value : default!; [return: MaybeNull]
public T GetValueOrDefault() => HasValue ? _value : default;
/// <summary> /// <summary>
/// Gets the value of the binding value if present, otherwise a default value. /// Gets the value of the binding value if present, otherwise a default value.
@ -206,11 +208,12 @@ namespace Avalonia.Data
/// The value if present and of the correct type, `default(TResult)` if the value is /// The value if present and of the correct type, `default(TResult)` if the value is
/// not present or of an incorrect type. /// not present or of an incorrect type.
/// </returns> /// </returns>
[return: MaybeNull]
public TResult GetValueOrDefault<TResult>() public TResult GetValueOrDefault<TResult>()
{ {
return HasValue ? return HasValue ?
_value is TResult result ? result : default! _value is TResult result ? result : default
: default!; : default;
} }
/// <summary> /// <summary>
@ -222,10 +225,11 @@ namespace Avalonia.Data
/// present but not of the correct type or null, or <paramref name="defaultValue"/> if the /// present but not of the correct type or null, or <paramref name="defaultValue"/> if the
/// value is not present. /// value is not present.
/// </returns> /// </returns>
public TResult GetValueOrDefault<TResult>(TResult defaultValue) [return: MaybeNull]
public TResult GetValueOrDefault<TResult>([AllowNull] TResult defaultValue)
{ {
return HasValue ? return HasValue ?
_value is TResult result ? result : default! _value is TResult result ? result : default
: defaultValue; : defaultValue;
} }
@ -242,7 +246,7 @@ namespace Avalonia.Data
UnsetValueType _ => Unset, UnsetValueType _ => Unset,
DoNothingType _ => DoNothing, DoNothingType _ => DoNothing,
BindingNotification n => n.ToBindingValue().Cast<T>(), BindingNotification n => n.ToBindingValue().Cast<T>(),
_ => (T)value! _ => new BindingValue<T>((T)value)
}; };
} }
@ -250,7 +254,7 @@ namespace Avalonia.Data
/// Creates a binding value from an instance of the underlying value type. /// Creates a binding value from an instance of the underlying value type.
/// </summary> /// </summary>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
public static implicit operator BindingValue<T>(T value) => new BindingValue<T>(value); public static implicit operator BindingValue<T>([AllowNull] T value) => new BindingValue<T>(value);
/// <summary> /// <summary>
/// Creates a binding value from an <see cref="Optional{T}"/>. /// Creates a binding value from an <see cref="Optional{T}"/>.
@ -259,18 +263,18 @@ namespace Avalonia.Data
public static implicit operator BindingValue<T>(Optional<T> optional) public static implicit operator BindingValue<T>(Optional<T> optional)
{ {
return optional.HasValue ? optional.Value! : Unset; return optional.HasValue ? optional.Value : Unset;
} }
/// <summary> /// <summary>
/// Returns a binding value with a type of <see cref="BindingValueType.UnsetValue"/>. /// Returns a binding value with a type of <see cref="BindingValueType.UnsetValue"/>.
/// </summary> /// </summary>
public static BindingValue<T> Unset => new BindingValue<T>(BindingValueType.UnsetValue, default!, null); public static BindingValue<T> Unset => new BindingValue<T>(BindingValueType.UnsetValue, default, null);
/// <summary> /// <summary>
/// Returns a binding value with a type of <see cref="BindingValueType.DoNothing"/>. /// Returns a binding value with a type of <see cref="BindingValueType.DoNothing"/>.
/// </summary> /// </summary>
public static BindingValue<T> DoNothing => new BindingValue<T>(BindingValueType.DoNothing, default!, null); public static BindingValue<T> DoNothing => new BindingValue<T>(BindingValueType.DoNothing, default, null);
/// <summary> /// <summary>
/// Returns a binding value with a type of <see cref="BindingValueType.BindingError"/>. /// Returns a binding value with a type of <see cref="BindingValueType.BindingError"/>.
@ -280,7 +284,7 @@ namespace Avalonia.Data
{ {
e = e ?? throw new ArgumentNullException(nameof(e)); e = e ?? throw new ArgumentNullException(nameof(e));
return new BindingValue<T>(BindingValueType.BindingError, default!, e); return new BindingValue<T>(BindingValueType.BindingError, default, e);
} }
/// <summary> /// <summary>
@ -309,7 +313,7 @@ namespace Avalonia.Data
fallbackValue.HasValue ? fallbackValue.HasValue ?
BindingValueType.BindingErrorWithFallback : BindingValueType.BindingErrorWithFallback :
BindingValueType.BindingError, BindingValueType.BindingError,
fallbackValue.HasValue ? fallbackValue.Value : default!, fallbackValue.HasValue ? fallbackValue.Value : default,
e); e);
} }
@ -321,7 +325,7 @@ namespace Avalonia.Data
{ {
e = e ?? throw new ArgumentNullException(nameof(e)); e = e ?? throw new ArgumentNullException(nameof(e));
return new BindingValue<T>(BindingValueType.DataValidationError, default!, e); return new BindingValue<T>(BindingValueType.DataValidationError, default, e);
} }
/// <summary> /// <summary>
@ -350,11 +354,11 @@ namespace Avalonia.Data
fallbackValue.HasValue ? fallbackValue.HasValue ?
BindingValueType.DataValidationErrorWithFallback : BindingValueType.DataValidationErrorWithFallback :
BindingValueType.DataValidationError, BindingValueType.DataValidationError,
fallbackValue.HasValue ? fallbackValue.Value : default!, fallbackValue.HasValue ? fallbackValue.Value : default,
e); e);
} }
private static void ValidateValue(T value) private static void ValidateValue([AllowNull] T value)
{ {
if (value is UnsetValueType) if (value is UnsetValueType)
{ {

26
src/Avalonia.Base/Data/Optional.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
#nullable enable #nullable enable
@ -22,13 +23,13 @@ namespace Avalonia.Data
/// </remarks> /// </remarks>
public readonly struct Optional<T> : IEquatable<Optional<T>> public readonly struct Optional<T> : IEquatable<Optional<T>>
{ {
private readonly T _value; [AllowNull] private readonly T _value;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Optional{T}"/> struct with value. /// Initializes a new instance of the <see cref="Optional{T}"/> struct with value.
/// </summary> /// </summary>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
public Optional(T value) public Optional([AllowNull] T value)
{ {
_value = value; _value = value;
HasValue = true; HasValue = true;
@ -48,7 +49,7 @@ namespace Avalonia.Data
public T Value => HasValue ? _value : throw new InvalidOperationException("Optional has no value."); public T Value => HasValue ? _value : throw new InvalidOperationException("Optional has no value.");
/// <inheritdoc/> /// <inheritdoc/>
public override bool Equals(object obj) => obj is Optional<T> o && this == o; public override bool Equals(object? obj) => obj is Optional<T> o && this == o;
/// <inheritdoc/> /// <inheritdoc/>
public bool Equals(Optional<T> other) => this == other; public bool Equals(Optional<T> other) => this == other;
@ -60,7 +61,7 @@ namespace Avalonia.Data
/// Casts the value (if any) to an <see cref="object"/>. /// Casts the value (if any) to an <see cref="object"/>.
/// </summary> /// </summary>
/// <returns>The cast optional value.</returns> /// <returns>The cast optional value.</returns>
public Optional<object> ToObject() => HasValue ? new Optional<object>(_value!) : default; public Optional<object> ToObject() => HasValue ? new Optional<object>(_value) : default;
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() => HasValue ? _value?.ToString() ?? "(null)" : "(empty)"; public override string ToString() => HasValue ? _value?.ToString() ?? "(null)" : "(empty)";
@ -69,7 +70,8 @@ namespace Avalonia.Data
/// Gets the value if present, otherwise the default value. /// Gets the value if present, otherwise the default value.
/// </summary> /// </summary>
/// <returns>The value.</returns> /// <returns>The value.</returns>
public T GetValueOrDefault() => HasValue ? _value : default!; [return: MaybeNull]
public T GetValueOrDefault() => HasValue ? _value : default;
/// <summary> /// <summary>
/// Gets the value if present, otherwise a default value. /// Gets the value if present, otherwise a default value.
@ -85,11 +87,12 @@ namespace Avalonia.Data
/// The value if present and of the correct type, `default(TResult)` if the value is /// The value if present and of the correct type, `default(TResult)` if the value is
/// not present or of an incorrect type. /// not present or of an incorrect type.
/// </returns> /// </returns>
[return: MaybeNull]
public TResult GetValueOrDefault<TResult>() public TResult GetValueOrDefault<TResult>()
{ {
return HasValue ? return HasValue ?
_value is TResult result ? result : default! _value is TResult result ? result : default
: default!; : default;
} }
/// <summary> /// <summary>
@ -101,10 +104,11 @@ namespace Avalonia.Data
/// present but not of the correct type or null, or <paramref name="defaultValue"/> if the /// present but not of the correct type or null, or <paramref name="defaultValue"/> if the
/// value is not present. /// value is not present.
/// </returns> /// </returns>
public TResult GetValueOrDefault<TResult>(TResult defaultValue) [return: MaybeNull]
public TResult GetValueOrDefault<TResult>([AllowNull] TResult defaultValue)
{ {
return HasValue ? return HasValue ?
_value is TResult result ? result : default! _value is TResult result ? result : default
: defaultValue; : defaultValue;
} }
@ -112,7 +116,7 @@ namespace Avalonia.Data
/// Creates an <see cref="Optional{T}"/> from an instance of the underlying value type. /// Creates an <see cref="Optional{T}"/> from an instance of the underlying value type.
/// </summary> /// </summary>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
public static implicit operator Optional<T>(T value) => new Optional<T>(value); public static implicit operator Optional<T>([AllowNull] T value) => new Optional<T>(value);
/// <summary> /// <summary>
/// Compares two <see cref="Optional{T}"/>s for inequality. /// Compares two <see cref="Optional{T}"/>s for inequality.
@ -128,7 +132,7 @@ namespace Avalonia.Data
/// <param name="x">The first value.</param> /// <param name="x">The first value.</param>
/// <param name="y">The second value.</param> /// <param name="y">The second value.</param>
/// <returns>True if the values are equal; otherwise false.</returns> /// <returns>True if the values are equal; otherwise false.</returns>
public static bool operator==(Optional<T> x, Optional<T> y) public static bool operator ==(Optional<T> x, Optional<T> y)
{ {
if (!x.HasValue && !y.HasValue) if (!x.HasValue && !y.HasValue)
{ {

4
src/Avalonia.Base/DirectPropertyBase.cs

@ -120,9 +120,9 @@ namespace Avalonia
return o.GetValue<TValue>(this); return o.GetValue<TValue>(this);
} }
internal override object RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority) internal override object? RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority)
{ {
return o.GetValue<TValue>(this)!; return o.GetValue<TValue>(this);
} }
/// <inheritdoc/> /// <inheritdoc/>

140
src/Avalonia.Base/Metadata/NullableAttributes.cs

@ -0,0 +1,140 @@
#pragma warning disable MA0048 // File name must match type name
#define INTERNAL_NULLABLE_ATTRIBUTES
#if NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48
// https://github.com/dotnet/corefx/blob/48363ac826ccf66fbe31a5dcb1dc2aab9a7dd768/src/Common/src/CoreLib/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace System.Diagnostics.CodeAnalysis
{
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class AllowNullAttribute : Attribute
{ }
/// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class DisallowNullAttribute : Attribute
{ }
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class MaybeNullAttribute : Attribute
{ }
/// <summary>Specifies that an output will not be null even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class NotNullAttribute : Attribute
{ }
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class MaybeNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter may be null.
/// </param>
public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class NotNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
/// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class NotNullIfNotNullAttribute : Attribute
{
/// <summary>Initializes the attribute with the associated parameter name.</summary>
/// <param name="parameterName">
/// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null.
/// </param>
public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName;
/// <summary>Gets the associated parameter name.</summary>
public string ParameterName { get; }
}
/// <summary>Applied to a method that will never return under any circumstance.</summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class DoesNotReturnAttribute : Attribute
{ }
/// <summary>Specifies that the method will not return if the associated Boolean parameter is passed the specified value.</summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class DoesNotReturnIfAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified parameter value.</summary>
/// <param name="parameterValue">
/// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to
/// the associated parameter matches this value.
/// </param>
public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue;
/// <summary>Gets the condition parameter value.</summary>
public bool ParameterValue { get; }
}
}
#endif

11
src/Avalonia.Base/PropertyStore/LocalValueEntry.cs

@ -1,4 +1,5 @@
using Avalonia.Data; using System.Diagnostics.CodeAnalysis;
using Avalonia.Data;
#nullable enable #nullable enable
@ -11,15 +12,15 @@ namespace Avalonia.PropertyStore
/// <typeparam name="T">The property type.</typeparam> /// <typeparam name="T">The property type.</typeparam>
internal class LocalValueEntry<T> : IValue<T> internal class LocalValueEntry<T> : IValue<T>
{ {
private T _value; [AllowNull] private T _value;
public LocalValueEntry(T value) => _value = value; public LocalValueEntry([AllowNull] T value) => _value = value;
public BindingPriority Priority => BindingPriority.LocalValue; public BindingPriority Priority => BindingPriority.LocalValue;
Optional<object> IValue.GetValue() => new Optional<object>(_value!); Optional<object> IValue.GetValue() => new Optional<object>(_value);
public Optional<T> GetValue(BindingPriority maxPriority) public Optional<T> GetValue(BindingPriority maxPriority)
{ {
return BindingPriority.LocalValue >= maxPriority ? _value! : Optional<T>.Empty; return BindingPriority.LocalValue >= maxPriority ? _value : Optional<T>.Empty;
} }
public void SetValue(T value) => _value = value; public void SetValue(T value) => _value = value;

2
src/Avalonia.Base/ValueStore.cs

@ -162,7 +162,7 @@ namespace Avalonia
_sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>( _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
_owner, _owner,
property, property,
old!, new Optional<T>(old),
default, default,
BindingPriority.Unset)); BindingPriority.Unset));
} }

Loading…
Cancel
Save