Browse Source

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

Steven Kirk 5 years ago
parent
commit
de6553a0dc

+ 24 - 20
src/Avalonia.Base/Data/BindingValue.cs

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

+ 15 - 11
src/Avalonia.Base/Data/Optional.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 
 #nullable enable
 
@@ -22,13 +23,13 @@ namespace Avalonia.Data
     /// </remarks>
     public readonly struct Optional<T> : IEquatable<Optional<T>>
     {
-        private readonly T _value;
+        [AllowNull] private readonly T _value;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="Optional{T}"/> struct with value.
         /// </summary>
         /// <param name="value">The value.</param>
-        public Optional(T value)
+        public Optional([AllowNull] T value)
         {
             _value = value;
             HasValue = true;
@@ -48,7 +49,7 @@ namespace Avalonia.Data
         public T Value => HasValue ? _value : throw new InvalidOperationException("Optional has no value.");
 
         /// <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/>
         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"/>.
         /// </summary>
         /// <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/>
         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.
         /// </summary>
         /// <returns>The value.</returns>
-        public T GetValueOrDefault() => HasValue ? _value : default!;
+        [return: MaybeNull]
+        public T GetValueOrDefault() => HasValue ? _value : default;
 
         /// <summary>
         /// 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
         /// not present or of an incorrect type.
         /// </returns>
+        [return: MaybeNull]
         public TResult GetValueOrDefault<TResult>()
         {
             return HasValue ?
-                _value is TResult result ? result : default!
-                : default!;
+                _value is TResult result ? result : default
+                : default;
         }
 
         /// <summary>
@@ -101,10 +104,11 @@ namespace Avalonia.Data
         /// present but not of the correct type or null, or <paramref name="defaultValue"/> if the
         /// value is not present.
         /// </returns>
-        public TResult GetValueOrDefault<TResult>(TResult defaultValue)
+        [return: MaybeNull]
+        public TResult GetValueOrDefault<TResult>([AllowNull] TResult defaultValue)
         {
             return HasValue ?
-                _value is TResult result ? result : default!
+                _value is TResult result ? result : default
                 : defaultValue;
         }
 
@@ -112,7 +116,7 @@ namespace Avalonia.Data
         /// Creates an <see cref="Optional{T}"/> from an instance of the underlying value type.
         /// </summary>
         /// <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>
         /// 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="y">The second value.</param>
         /// <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)
             {

+ 2 - 2
src/Avalonia.Base/DirectPropertyBase.cs

@@ -120,9 +120,9 @@ namespace Avalonia
             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/>

+ 140 - 0
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

+ 6 - 5
src/Avalonia.Base/PropertyStore/LocalValueEntry.cs

@@ -1,4 +1,5 @@
-using Avalonia.Data;
+using System.Diagnostics.CodeAnalysis;
+using Avalonia.Data;
 
 #nullable enable
 
@@ -11,15 +12,15 @@ namespace Avalonia.PropertyStore
     /// <typeparam name="T">The property type.</typeparam>
     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;
-        Optional<object> IValue.GetValue() => new Optional<object>(_value!);
+        Optional<object> IValue.GetValue() => new Optional<object>(_value);
         
         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;

+ 1 - 1
src/Avalonia.Base/ValueStore.cs

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