-
Notifications
You must be signed in to change notification settings - Fork 422
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
Nullability annotations for bindables #5099
base: master
Are you sure you want to change the base?
Changes from 15 commits
7191799
3c3f9d7
1028d75
53abe18
6650687
3e88848
fe5b8b6
8e37160
6ada9f5
abd2caa
706cfe7
8f7fb1b
7b1594f
5a40906
36a0f6e
b5becda
56de9b4
67ece5a
132c64d
0045365
db7c54a
6804bda
9c9b8ad
62deab7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,6 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
#nullable disable | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
@@ -31,7 +29,7 @@ public class AggregateBindable<T> | |
/// </summary> | ||
/// <param name="aggregateFunction">The function to be used for aggregation, taking two input <typeparamref name="T"/> values and returning one output.</param> | ||
/// <param name="resultBindable">An optional newly constructed bindable to use for <see cref="Result"/>. The initial value of this bindable is used as the initial value for the aggregate.</param> | ||
public AggregateBindable(Func<T, T, T> aggregateFunction, Bindable<T> resultBindable = null) | ||
public AggregateBindable(Func<T, T, T> aggregateFunction, Bindable<T>? resultBindable = null) | ||
{ | ||
this.aggregateFunction = aggregateFunction; | ||
result = resultBindable ?? new Bindable<T>(); | ||
|
@@ -77,10 +75,10 @@ public void RemoveSource(IBindable<T> bindable) | |
} | ||
} | ||
|
||
private WeakRefPair findExistingPair(IBindable<T> bindable) => | ||
private WeakRefPair? findExistingPair(IBindable<T> bindable) => | ||
sourceMapping.FirstOrDefault(p => p.WeakReference.TryGetTarget(out var target) && target == bindable); | ||
|
||
private void recalculateAggregate(ValueChangedEvent<T> obj = null) | ||
private void recalculateAggregate(ValueChangedEvent<T>? obj = null) | ||
{ | ||
T calculated = initialValue; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,14 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
#nullable disable | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Globalization; | ||
using System.Linq; | ||
using JetBrains.Annotations; | ||
using Newtonsoft.Json; | ||
using osu.Framework.Extensions.ObjectExtensions; | ||
using osu.Framework.Extensions.TypeExtensions; | ||
using osu.Framework.IO.Serialization; | ||
using osu.Framework.Lists; | ||
|
@@ -24,17 +24,17 @@ public class Bindable<T> : IBindable<T>, IBindable, IParseable, ISerializableBin | |
/// <summary> | ||
/// An event which is raised when <see cref="Value"/> has changed (or manually via <see cref="TriggerValueChange"/>). | ||
/// </summary> | ||
public event Action<ValueChangedEvent<T>> ValueChanged; | ||
public event Action<ValueChangedEvent<T>>? ValueChanged; | ||
|
||
/// <summary> | ||
/// An event which is raised when <see cref="Disabled"/> has changed (or manually via <see cref="TriggerDisabledChange"/>). | ||
/// </summary> | ||
public event Action<bool> DisabledChanged; | ||
public event Action<bool>? DisabledChanged; | ||
|
||
/// <summary> | ||
/// An event which is raised when <see cref="Default"/> has changed (or manually via <see cref="TriggerDefaultChange"/>). | ||
/// </summary> | ||
public event Action<ValueChangedEvent<T>> DefaultChanged; | ||
public event Action<ValueChangedEvent<T>>? DefaultChanged; | ||
|
||
private T value; | ||
|
||
|
@@ -59,7 +59,7 @@ public virtual bool Disabled | |
} | ||
} | ||
|
||
internal void SetDisabled(bool value, bool bypassChecks = false, Bindable<T> source = null) | ||
internal void SetDisabled(bool value, bool bypassChecks = false, Bindable<T>? source = null) | ||
{ | ||
if (!bypassChecks) | ||
throwIfLeased(); | ||
|
@@ -90,15 +90,15 @@ public virtual T Value | |
// if the leased bindable decides to disable exclusive access (by setting Disabled = false) then anything will be able to write to Value. | ||
|
||
if (Disabled) | ||
throw new InvalidOperationException($"Can not set value to \"{value.ToString()}\" as bindable is disabled."); | ||
throw new InvalidOperationException($"Can not set value to \"{value}\" as bindable is disabled."); | ||
|
||
if (EqualityComparer<T>.Default.Equals(this.value, value)) return; | ||
|
||
SetValue(this.value, value); | ||
} | ||
} | ||
|
||
internal void SetValue(T previousValue, T value, bool bypassChecks = false, Bindable<T> source = null) | ||
internal void SetValue(T previousValue, T value, bool bypassChecks = false, Bindable<T>? source = null) | ||
{ | ||
this.value = value; | ||
TriggerValueChange(previousValue, source ?? this, true, bypassChecks); | ||
|
@@ -116,21 +116,21 @@ public virtual T Default | |
// if the leased bindable decides to disable exclusive access (by setting Disabled = false) then anything will be able to write to Default. | ||
|
||
if (Disabled) | ||
throw new InvalidOperationException($"Can not set default value to \"{value.ToString()}\" as bindable is disabled."); | ||
throw new InvalidOperationException($"Can not set default value to \"{value}\" as bindable is disabled."); | ||
|
||
if (EqualityComparer<T>.Default.Equals(defaultValue, value)) return; | ||
|
||
SetDefaultValue(defaultValue, value); | ||
} | ||
} | ||
|
||
internal void SetDefaultValue(T previousValue, T value, bool bypassChecks = false, Bindable<T> source = null) | ||
internal void SetDefaultValue(T previousValue, T value, bool bypassChecks = false, Bindable<T>? source = null) | ||
{ | ||
defaultValue = value; | ||
TriggerDefaultChange(previousValue, source ?? this, true, bypassChecks); | ||
} | ||
|
||
private WeakReference<Bindable<T>> weakReferenceInstance; | ||
private WeakReference<Bindable<T>>? weakReferenceInstance; | ||
|
||
private WeakReference<Bindable<T>> weakReference => weakReferenceInstance ??= new WeakReference<Bindable<T>>(this); | ||
|
||
|
@@ -139,20 +139,22 @@ internal void SetDefaultValue(T previousValue, T value, bool bypassChecks = fals | |
/// </summary> | ||
[UsedImplicitly] | ||
private Bindable() | ||
: this(default) | ||
: this(default!) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Creates a new bindable instance initialised with a default value. | ||
/// </summary> | ||
/// <param name="defaultValue">The initial and default value for this bindable.</param> | ||
public Bindable(T defaultValue = default) | ||
public Bindable(T defaultValue = default!) | ||
{ | ||
value = Default = defaultValue; | ||
// TODO: add a custom analyser warning about no default value provided for non-nullable T | ||
// remember to also check for derived class constructors | ||
value = this.defaultValue = defaultValue; | ||
} | ||
|
||
protected LockedWeakList<Bindable<T>> Bindings { get; private set; } | ||
protected LockedWeakList<Bindable<T>>? Bindings { get; private set; } | ||
|
||
void IBindable.BindTo(IBindable them) | ||
{ | ||
|
@@ -254,7 +256,7 @@ public virtual void Parse(object input) | |
|
||
default: | ||
if (underlyingType.IsEnum) | ||
Value = (T)Enum.Parse(underlyingType, input.ToString()); | ||
Value = (T)Enum.Parse(underlyingType, input.ToString().AsNonNull()); | ||
else | ||
Value = (T)Convert.ChangeType(input, underlyingType, CultureInfo.InvariantCulture); | ||
|
||
|
@@ -361,8 +363,9 @@ public void UnbindBindings() | |
|
||
internal virtual void UnbindAllInternal() | ||
{ | ||
// TODO: annotate isLeased with [MemberNotNull(nameof(leasedBindable))] on .NET 5+ to satisfy the nullability check | ||
if (isLeased) | ||
leasedBindable.Return(); | ||
leasedBindable.AsNonNull().Return(); | ||
|
||
UnbindEvents(); | ||
UnbindBindings(); | ||
|
@@ -377,7 +380,7 @@ public virtual void UnbindFrom(IUnbindable them) | |
tThem.removeWeakReference(weakReference); | ||
} | ||
|
||
public string Description { get; set; } | ||
public string Description { get; set; } = string.Empty; | ||
|
||
public override string ToString() => value?.ToString() ?? string.Empty; | ||
|
||
|
@@ -410,10 +413,13 @@ void ISerializableBindable.SerializeTo(JsonWriter writer, JsonSerializer seriali | |
|
||
void ISerializableBindable.DeserializeFrom(JsonReader reader, JsonSerializer serializer) | ||
{ | ||
Value = serializer.Deserialize<T>(reader); | ||
// Deserialize returns null for json literal "null". | ||
var result = serializer.Deserialize<T>(reader); | ||
Debug.Assert(result != null); | ||
Value = result; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume this is for a case like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The last change was by @frenzibyte There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deserialization is a well-known hole of nullable annotations. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note for you: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait for #5296 then use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @smoogipoo I've already updated this. Is there still any issue remaining? |
||
} | ||
|
||
private LeasedBindable<T> leasedBindable; | ||
private LeasedBindable<T>? leasedBindable; | ||
|
||
private bool isLeased => leasedBindable != null; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,6 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
#nullable disable | ||
|
||
namespace osu.Framework.Bindables | ||
{ | ||
public class BindableBool : Bindable<bool> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,6 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
#nullable disable | ||
|
||
using System.Globalization; | ||
|
||
namespace osu.Framework.Bindables | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,6 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
#nullable disable | ||
|
||
using System.Globalization; | ||
|
||
namespace osu.Framework.Bindables | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,6 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
#nullable disable | ||
|
||
using System.Globalization; | ||
|
||
namespace osu.Framework.Bindables | ||
|
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.
I see this TODO as mandatory before this PR can be merged. The implications of this can go as far as to end up merging PRs that can nullref in release builds because
v.OldValue
/v.NewValue
usages were promised to be not null while in reality it is, due to the forgiving we're doing here.I noticed there were previous discussions above leading to having the analyzer as follow-up effort, but I'm really not sure about that. @ppy/team-client can I have your opinions on this matter? am I the only one who thinks this cannot be merged as-is?