Browse Source

Config generation now uses Nullable annotations

pull/74/head
Anairkoen Schno 1 year ago
parent
commit
fc9759ff36
Signed by: DaNike GPG Key ID: BEFB74D5F3FC4387
24 changed files with 367 additions and 249 deletions
  1. +2
    -3
      BSIPA.sln
  2. +8
    -0
      Common.props
  3. +0
    -4
      Common.targets
  4. +16
    -14
      IPA.Loader/Config/Data/List.cs
  5. +19
    -18
      IPA.Loader/Config/Data/Map.cs
  6. +83
    -7
      IPA.Loader/Config/Data/Primitives.cs
  7. +20
    -14
      IPA.Loader/Config/Data/Value.cs
  8. +1
    -1
      IPA.Loader/Config/SelfConfig.cs
  9. +15
    -5
      IPA.Loader/Config/Stores/Attributes.cs
  10. +17
    -16
      IPA.Loader/Config/Stores/CollectionConverter.cs
  11. +75
    -74
      IPA.Loader/Config/Stores/Converters.cs
  12. +17
    -14
      IPA.Loader/Config/Stores/CustomObjectConverter.cs
  13. +5
    -4
      IPA.Loader/Config/Stores/GeneratedStoreImpl/ConversionDelegates.cs
  14. +2
    -1
      IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs
  15. +6
    -1
      IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs
  16. +15
    -11
      IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs
  17. +21
    -20
      IPA.Loader/Config/Stores/GeneratedStoreImpl/IGeneratedStore.cs
  18. +5
    -4
      IPA.Loader/Config/Stores/GeneratedStoreImpl/MakeCreator.cs
  19. +20
    -15
      IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs
  20. +2
    -1
      IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs
  21. +2
    -1
      IPA.Loader/Config/Stores/GeneratedStorePublicInterface.cs
  22. +8
    -7
      IPA.Loader/Config/Stores/ValueConverter.cs
  23. +8
    -4
      IPA.Loader/Utilities/AlmostVersion.cs
  24. +0
    -10
      IPA/_Attributes.cs

+ 2
- 3
BSIPA.sln View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28729.10
# Visual Studio Version 17
VisualStudioVersion = 17.1.31911.260
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IPA", "IPA\IPA.csproj", "{14092533-98BB-40A4-9AFC-27BB75672A70}"
ProjectSection(ProjectDependencies) = postProject
@ -24,7 +24,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.github\workflows\docs.yml = .github\workflows\docs.yml
README.md = README.md
.github\release_draft.yml = .github\release_draft.yml
System.Diagnostics.CodeAnalysis.cs = System.Diagnostics.CodeAnalysis.cs
.github\workflows\tag_docs.yml = .github\workflows\tag_docs.yml
EndProjectSection
EndProject


+ 8
- 0
Common.props View File

@ -14,6 +14,14 @@
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" />
<PackageReference Include="Nullable" Version="1.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="IsExternalInit" Version="1.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

+ 0
- 4
Common.targets View File

@ -1,8 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)\System.Diagnostics.CodeAnalysis.cs"/>
</ItemGroup>
</Project>

+ 16
- 14
IPA.Loader/Config/Data/List.cs View File

@ -1,4 +1,5 @@
using IPA.Utilities;
#nullable enable
using IPA.Utilities;
using System;
using System.Collections;
using System.Collections.Generic;
@ -10,9 +11,9 @@ namespace IPA.Config.Data
/// A list of <see cref="Value"/>s for serialization by an <see cref="IConfigProvider"/>.
/// Use <see cref="Value.List"/> or <see cref="Value.From(IEnumerable{Value})"/> to create.
/// </summary>
public sealed class List : Value, IList<Value>
public sealed class List : Value, IList<Value?>
{
private readonly List<Value> values = new List<Value>();
private readonly List<Value?> values = new();
internal List() { }
@ -22,7 +23,7 @@ namespace IPA.Config.Data
/// <param name="index">the index to retrieve the <see cref="Value"/> at</param>
/// <returns>the <see cref="Value"/> at <paramref name="index"/></returns>
/// <seealso cref="IList{T}.this[int]"/>
public Value this[int index] { get => values[index]; set => values[index] = value; }
public Value? this[int index] { get => values[index]; set => values[index] = value; }
/// <summary>
/// Gets the number of elements in the <see cref="List"/>.
@ -30,21 +31,22 @@ namespace IPA.Config.Data
/// <seealso cref="ICollection{T}.Count"/>
public int Count => values.Count;
bool ICollection<Value>.IsReadOnly => ((IList<Value>)values).IsReadOnly;
bool ICollection<Value?>.IsReadOnly => ((IList<Value?>)values).IsReadOnly;
/// <summary>
/// Adds a <see cref="Value"/> to the end of this <see cref="List"/>.
/// </summary>
/// <param name="item">the <see cref="Value"/> to add</param>
/// <seealso cref="ICollection{T}.Add(T)"/>
public void Add(Value item) => values.Add(item);
public void Add(Value? item) => values.Add(item);
/// <summary>
/// Adds a range of <see cref="Value"/>s to the end of this <see cref="List"/>.
/// </summary>
/// <param name="vals">the range of <see cref="Value"/>s to add</param>
public void AddRange(IEnumerable<Value> vals)
public void AddRange(IEnumerable<Value?> vals)
{
if (vals is null) throw new ArgumentNullException(nameof(vals));
foreach (var val in vals) Add(val);
}
@ -60,7 +62,7 @@ namespace IPA.Config.Data
/// <param name="item">the <see cref="Value"/> to check for</param>
/// <returns><see langword="true"/> if the item was founc, otherwise <see langword="false"/></returns>
/// <seealso cref="ICollection{T}.Contains(T)"/>
public bool Contains(Value item) => values.Contains(item);
public bool Contains(Value? item) => values.Contains(item);
/// <summary>
/// Copies the <see cref="Value"/>s in the <see cref="List"/> to the <see cref="Array"/> in <paramref name="array"/>.
@ -68,14 +70,14 @@ namespace IPA.Config.Data
/// <param name="array">the <see cref="Array"/> to copy to</param>
/// <param name="arrayIndex">the starting index to copy to</param>
/// <seealso cref="ICollection{T}.CopyTo(T[], int)"/>
public void CopyTo(Value[] array, int arrayIndex) => values.CopyTo(array, arrayIndex);
public void CopyTo(Value?[] array, int arrayIndex) => values.CopyTo(array, arrayIndex);
/// <summary>
/// Gets an enumerator to enumerate the <see cref="List"/>.
/// </summary>
/// <returns>an <see cref="IEnumerator{T}"/> for this <see cref="List"/></returns>
/// <seealso cref="IEnumerable{T}.GetEnumerator"/>
public IEnumerator<Value> GetEnumerator() => ((IList<Value>)values).GetEnumerator();
public IEnumerator<Value?> GetEnumerator() => ((IList<Value?>)values).GetEnumerator();
/// <summary>
/// Gets the index that a given <see cref="Value"/> is in the <see cref="List"/>.
@ -83,7 +85,7 @@ namespace IPA.Config.Data
/// <param name="item">the <see cref="Value"/> to search for</param>
/// <returns>the index that the <paramref name="item"/> was at, or -1.</returns>
/// <seealso cref="IList{T}.IndexOf(T)"/>
public int IndexOf(Value item) => values.IndexOf(item);
public int IndexOf(Value? item) => values.IndexOf(item);
/// <summary>
/// Inserts a <see cref="Value"/> at an index.
@ -91,7 +93,7 @@ namespace IPA.Config.Data
/// <param name="index">the index to insert at</param>
/// <param name="item">the <see cref="Value"/> to insert</param>
/// <seealso cref="IList{T}.Insert(int, T)"/>
public void Insert(int index, Value item) => values.Insert(index, item);
public void Insert(int index, Value? item) => values.Insert(index, item);
/// <summary>
/// Removes a <see cref="Value"/> from the <see cref="List"/>.
@ -99,7 +101,7 @@ namespace IPA.Config.Data
/// <param name="item">the <see cref="Value"/> to remove</param>
/// <returns><see langword="true"/> if the item was removed, <see langword="false"/> otherwise</returns>
/// <seealso cref="ICollection{T}.Remove(T)"/>
public bool Remove(Value item) => values.Remove(item);
public bool Remove(Value? item) => values.Remove(item);
/// <summary>
/// Removes a <see cref="Value"/> at an index.
@ -115,7 +117,7 @@ namespace IPA.Config.Data
public override string ToString()
=> $"[{string.Join(",",this.Select(v => v?.ToString() ?? "null").StrJP())}]";
IEnumerator IEnumerable.GetEnumerator() => ((IList<Value>)values).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}


+ 19
- 18
IPA.Loader/Config/Data/Map.cs View File

@ -1,4 +1,5 @@
using IPA.Utilities;
#nullable enable
using IPA.Utilities;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@ -10,10 +11,10 @@ namespace IPA.Config.Data
/// A ordered map of <see cref="string"/> to <see cref="Value"/> for serialization by an <see cref="IConfigProvider"/>.
/// Use <see cref="Value.Map"/> or <see cref="Value.From(IDictionary{string, Value})"/> to create.
/// </summary>
public sealed class Map : Value, IDictionary<string, Value>
public sealed class Map : Value, IDictionary<string, Value?>
{
private readonly Dictionary<string, Value> values = new Dictionary<string, Value>();
private readonly List<string> keyOrder = new List<string>();
private readonly Dictionary<string, Value?> values = new();
private readonly List<string> keyOrder = new();
internal Map() { }
@ -23,7 +24,7 @@ namespace IPA.Config.Data
/// <param name="key">the key to get the value associated with</param>
/// <returns>the value associated with the <paramref name="key"/></returns>
/// <seealso cref="IDictionary{TKey, TValue}.this[TKey]"/>
public Value this[string key] { get => values[key]; set => values[key] = value; }
public Value? this[string key] { get => values[key]; set => values[key] = value; }
/// <summary>
/// Gets a collection of the keys for the <see cref="Map"/>.
@ -39,7 +40,7 @@ namespace IPA.Config.Data
/// guarantee that order is maintained.
/// </remarks>
/// <seealso cref="IDictionary{TKey, TValue}.Values"/>
public ICollection<Value> Values => values.Values;
public ICollection<Value?> Values => values.Values;
/// <summary>
/// Gets the number of key-value pairs in this <see cref="Map"/>.
@ -47,7 +48,7 @@ namespace IPA.Config.Data
/// <seealso cref="ICollection{T}.Count"/>
public int Count => values.Count;
bool ICollection<KeyValuePair<string, Value>>.IsReadOnly => ((IDictionary<string, Value>)values).IsReadOnly;
bool ICollection<KeyValuePair<string, Value?>>.IsReadOnly => ((IDictionary<string, Value?>)values).IsReadOnly;
/// <summary>
/// Adds a new <see cref="Value"/> with a given key.
@ -55,13 +56,13 @@ namespace IPA.Config.Data
/// <param name="key">the key to put the value at</param>
/// <param name="value">the <see cref="Value"/> to add</param>
/// <seealso cref="IDictionary{TKey, TValue}.Add(TKey, TValue)"/>
public void Add(string key, Value value)
public void Add(string key, Value? value)
{
values.Add(key, value);
keyOrder.Add(key);
}
void ICollection<KeyValuePair<string, Value>>.Add(KeyValuePair<string, Value> item)
void ICollection<KeyValuePair<string, Value?>>.Add(KeyValuePair<string, Value?> item)
=> Add(item.Key, item.Value);
/// <summary>
@ -74,8 +75,8 @@ namespace IPA.Config.Data
keyOrder.Clear();
}
bool ICollection<KeyValuePair<string, Value>>.Contains(KeyValuePair<string, Value> item)
=> ((IDictionary<string, Value>)values).Contains(item);
bool ICollection<KeyValuePair<string, Value?>>.Contains(KeyValuePair<string, Value?> item)
=> ((IDictionary<string, Value?>)values).Contains(item);
/// <summary>
/// Checks if the <see cref="Map"/> contains a given <paramref name="key"/>.
@ -85,18 +86,18 @@ namespace IPA.Config.Data
/// <seealso cref="IDictionary{TKey, TValue}.ContainsKey(TKey)"/>
public bool ContainsKey(string key) => values.ContainsKey(key);
void ICollection<KeyValuePair<string, Value>>.CopyTo(KeyValuePair<string, Value>[] array, int arrayIndex)
=> ((IDictionary<string, Value>)values).CopyTo(array, arrayIndex);
void ICollection<KeyValuePair<string, Value?>>.CopyTo(KeyValuePair<string, Value?>[] array, int arrayIndex)
=> ((IDictionary<string, Value?>)values).CopyTo(array, arrayIndex);
/// <summary>
/// Enumerates the <see cref="Map"/>'s key-value pairs.
/// </summary>
/// <returns>an <see cref="IEnumerator{T}"/> of key-value pairs in this <see cref="Map"/></returns>
/// <seealso cref="IEnumerable{T}.GetEnumerator()"/>
public IEnumerator<KeyValuePair<string, Value>> GetEnumerator()
public IEnumerator<KeyValuePair<string, Value?>> GetEnumerator()
{
foreach (var key in keyOrder)
yield return new KeyValuePair<string, Value>(key, this[key]);
yield return new KeyValuePair<string, Value?>(key, this[key]);
}
/// <summary>
@ -107,8 +108,8 @@ namespace IPA.Config.Data
/// <seealso cref="IDictionary{TKey, TValue}.Remove(TKey)"/>
public bool Remove(string key) => values.Remove(key) && keyOrder.Remove(key);
bool ICollection<KeyValuePair<string, Value>>.Remove(KeyValuePair<string, Value> item)
=> ((IDictionary<string, Value>)values).Remove(item) && (keyOrder.Remove(item.Key) || true);
bool ICollection<KeyValuePair<string, Value?>>.Remove(KeyValuePair<string, Value?> item)
=> ((IDictionary<string, Value?>)values).Remove(item) && (keyOrder.Remove(item.Key) || true);
/// <summary>
/// Gets the value associated with the specified key.
@ -117,7 +118,7 @@ namespace IPA.Config.Data
/// <param name="value">the target location of the retrieved object</param>
/// <returns><see langword="true"/> if the key was found and <paramref name="value"/> set, <see langword="false"/> otherwise</returns>
/// <seealso cref="IDictionary{TKey, TValue}.TryGetValue(TKey, out TValue)"/>
public bool TryGetValue(string key, out Value value) => values.TryGetValue(key, out value);
public bool TryGetValue(string key, out Value? value) => values.TryGetValue(key, out value);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();


+ 83
- 7
IPA.Loader/Config/Data/Primitives.cs View File

@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -12,10 +13,28 @@ namespace IPA.Config.Data
/// </summary>
public sealed class Text : Value
{
/// <summary>
/// Constructs an empty <see cref="Text"/> object.
/// </summary>
[Obsolete("Use the String constructor.")]
public Text()
{
Value = null!;
}
/// <summary>
/// Constructs a <see cref="Text"/> object containing the provided value.
/// </summary>
/// <param name="value">The value to construct with.</param>
public Text(string value)
{
Value = value;
}
/// <summary>
/// The actual value of this <see cref="Text"/> object.
/// </summary>
public string Value { get; set; }
public string Value { get; init; }
/// <summary>
/// Converts this <see cref="Data.Value"/> into a human-readable format.
@ -30,6 +49,24 @@ namespace IPA.Config.Data
/// </summary>
public sealed class Integer : Value
{
/// <summary>
/// Constructs an empty <see cref="Integer"/> object.
/// </summary>
[Obsolete("Use the long constructor.")]
public Integer()
{
Value = 0;
}
/// <summary>
/// Constructs a <see cref="Integer"/> object containing the provided value.
/// </summary>
/// <param name="value">The value to construct with.</param>
public Integer(long value)
{
Value = value;
}
/// <summary>
/// The actual value of the <see cref="Integer"/> object.
/// </summary>
@ -50,10 +87,28 @@ namespace IPA.Config.Data
/// <summary>
/// A <see cref="Value"/> representing a floating point value. This may hold a
/// <see cref="decimal"/>'s worth of data.
/// <see cref="decimal"/>'s worth of data.
/// </summary>
public sealed class FloatingPoint : Value
{
/// <summary>
/// Constructs an empty <see cref="FloatingPoint"/> object.
/// </summary>
[Obsolete("Use the long constructor.")]
public FloatingPoint()
{
Value = 0;
}
/// <summary>
/// Constructs a <see cref="FloatingPoint"/> object containing the provided value.
/// </summary>
/// <param name="value">The value to construct with.</param>
public FloatingPoint(decimal value)
{
Value = value;
}
/// <summary>
/// The actual value fo this <see cref="FloatingPoint"/> object.
/// </summary>
@ -76,16 +131,37 @@ namespace IPA.Config.Data
/// A <see cref="Value"/> representing a boolean value.
/// </summary>
public sealed class Boolean : Value
{
{
/// <summary>
/// Constructs an empty <see cref="Boolean"/> object.
/// </summary>
[Obsolete("Use the long constructor.")]
public Boolean()
{
Value = false;
}
/// <summary>
/// Constructs a <see cref="Boolean"/> object containing the provided value.
/// </summary>
/// <param name="value">The value to construct with.</param>
public Boolean(bool value)
{
Value = value;
}
/// <summary>
/// The actual value fo this <see cref="Boolean"/> object.
/// </summary>
public bool Value { get; set; }
public bool Value { get; set; }
/// <summary>
/// Converts this <see cref="Data.Value"/> into a human-readable format.
/// </summary>
/// <returns>the result of <c>Value.ToString().ToLower()</c></returns>
public override string ToString() => Value.ToString().ToLower();
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase",
Justification = "ToLower is the desired display value.")]
public override string ToString() => Value.ToString().ToLower(System.Globalization.CultureInfo.InvariantCulture);
}
}

+ 20
- 14
IPA.Loader/Config/Data/Value.cs View File

@ -1,6 +1,8 @@
using System;
#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@ -25,21 +27,21 @@ namespace IPA.Config.Data
/// Creates a Null <see cref="Value"/>.
/// </summary>
/// <returns><see langword="null"/></returns>
public static Value Null() => null;
public static Value? Null() => null;
/// <summary>
/// Creates an empty <see cref="List"/>.
/// </summary>
/// <returns>an empty <see cref="List"/></returns>
/// <seealso cref="From(IEnumerable{Value})"/>
public static List List() => new List();
public static List List() => new();
/// <summary>
/// Creates an empty <see cref="Map"/>.
/// </summary>
/// <returns>an empty <see cref="Map"/></returns>
/// <seealso cref="From(IDictionary{string, Value})"/>
/// <seealso cref="From(IEnumerable{KeyValuePair{string, Value}})"/>
public static Map Map() => new Map();
public static Map Map() => new();
/// <summary>
/// Creates a new <see cref="Value"/> representing a <see cref="string"/>.
@ -47,14 +49,16 @@ namespace IPA.Config.Data
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="Data.Text"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="Text(string)"/>
public static Text From(string val) => Text(val);
[return: NotNullIfNotNull("val")]
public static Text? From(string? val) => Text(val);
/// <summary>
/// Creates a new <see cref="Data.Text"/> object wrapping a <see cref="string"/>.
/// </summary>
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="Data.Text"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="From(string)"/>
public static Text Text(string val) => val == null ? null : new Text { Value = val };
[return: NotNullIfNotNull("val")]
public static Text? Text(string? val) => val == null ? null : new(val);
/// <summary>
/// Creates a new <see cref="Value"/> wrapping a <see cref="long"/>.
@ -69,7 +73,7 @@ namespace IPA.Config.Data
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="Data.Integer"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="From(long)"/>
public static Integer Integer(long val) => new Integer { Value = val };
public static Integer Integer(long val) => new(val);
/// <summary>
/// Creates a new <see cref="Value"/> wrapping a <see cref="double"/>.
@ -84,7 +88,7 @@ namespace IPA.Config.Data
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="FloatingPoint"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="From(decimal)"/>
public static FloatingPoint Float(decimal val) => new FloatingPoint { Value = val };
public static FloatingPoint Float(decimal val) => new(val);
/// <summary>
/// Creates a new <see cref="Value"/> wrapping a <see cref="bool"/>.
@ -99,7 +103,7 @@ namespace IPA.Config.Data
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="Boolean"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="From(bool)"/>
public static Boolean Bool(bool val) => new Boolean { Value = val };
public static Boolean Bool(bool val) => new(val);
/// <summary>
/// Creates a new <see cref="Data.List"/> holding the content of an <see cref="IEnumerable{T}"/>
@ -108,9 +112,10 @@ namespace IPA.Config.Data
/// <param name="vals">the <see cref="Value"/>s to initialize the <see cref="Data.List"/> with</param>
/// <returns>a <see cref="Data.List"/> containing the content of <paramref name="vals"/></returns>
/// <seealso cref="List"/>
public static List From(IEnumerable<Value> vals)
[return: NotNullIfNotNull("vals")]
public static List? From(IEnumerable<Value?>? vals)
{
if (vals == null) return null;
if (vals is null) return null;
var l = List();
l.AddRange(vals);
return l;
@ -124,7 +129,7 @@ namespace IPA.Config.Data
/// <returns>a <see cref="Data.Map"/> containing the content of <paramref name="vals"/></returns>
/// <seealso cref="Map"/>
/// <seealso cref="From(IEnumerable{KeyValuePair{string, Value}})"/>
public static Map From(IDictionary<string, Value> vals) => From(vals as IEnumerable<KeyValuePair<string, Value>>);
public static Map From(IDictionary<string, Value?> vals) => From(vals as IEnumerable<KeyValuePair<string, Value?>>);
/// <summary>
/// Creates a new <see cref="Data.Map"/> holding the content of an <see cref="IEnumerable{T}"/>
@ -134,9 +139,10 @@ namespace IPA.Config.Data
/// <returns>a <see cref="Data.Map"/> containing the content of <paramref name="vals"/></returns>
/// <seealso cref="Map"/>
/// <seealso cref="From(IDictionary{string, Value})"/>
public static Map From(IEnumerable<KeyValuePair<string, Value>> vals)
[return: NotNullIfNotNull("vals")]
public static Map? From(IEnumerable<KeyValuePair<string, Value?>>? vals)
{
if (vals == null) return null;
if (vals is null) return null;
var m = Map();
foreach (var v in vals) m.Add(v.Key, v.Value);
return m;


+ 1
- 1
IPA.Loader/Config/SelfConfig.cs View File

@ -175,7 +175,7 @@ namespace IPA.Config
&& CommandLineValues.YeetMods;
// LINE: ignore
[NonNullable, UseConverter(typeof(CollectionConverter<string, HashSet<string>>))]
[NonNullable, UseConverter(typeof(CollectionConverter<string, HashSet<string?>>))]
public virtual HashSet<string> GameAssemblies { get; set; } = new HashSet<string>
{
// LINE: ignore 5


+ 15
- 5
IPA.Loader/Config/Stores/Attributes.cs View File

@ -1,6 +1,8 @@
using IPA.Config.Stores.Converters;
#nullable enable
using IPA.Config.Stores.Converters;
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace IPA.Config.Stores.Attributes
@ -37,23 +39,25 @@ namespace IPA.Config.Stores.Attributes
/// <summary>
/// Gets whether or not to use the default converter for the member type instead of the specified type.
/// </summary>
[MemberNotNullWhen(false, nameof(ConverterType))]
public bool UseDefaultConverterForType { get; }
/// <summary>
/// Gets the type of the converter to use.
/// </summary>
public Type ConverterType { get; }
public Type? ConverterType { get; }
/// <summary>
/// Gets the target type of the converter if it is avaliable at instantiation time, otherwise
/// <see langword="null"/>.
/// </summary>
public Type ConverterTargetType { get; }
public Type? ConverterTargetType { get; }
/// <summary>
/// Gets whether or not this converter is a generic <see cref="ValueConverter{T}"/>.
/// </summary>
public bool IsGenericConverter => ConverterTargetType != null;
[MemberNotNullWhen(true, nameof(ConverterTargetType))]
public bool IsGenericConverter => ConverterTargetType is not null;
/// <summary>
/// Creates a new <see cref="UseConverterAttribute"/> specifying to use the default converter type for the target member.
@ -67,11 +71,17 @@ namespace IPA.Config.Stores.Attributes
/// <param name="converterType">the type to assign to <see cref="ConverterType"/></param>
public UseConverterAttribute(Type converterType)
{
if (converterType is null)
throw new ArgumentNullException(nameof(converterType));
UseDefaultConverterForType = false;
ConverterType = converterType;
if (converterType.IsValueType)
throw new ArgumentException("Type is not a value converter!");
var baseT = ConverterType.BaseType;
while (baseT != null && baseT != typeof(object) &&
while (baseT != typeof(object) &&
(!baseT.IsGenericType || baseT.GetGenericTypeDefinition() != typeof(ValueConverter<>)))
baseT = baseT.BaseType;
if (baseT == typeof(object)) ConverterTargetType = null;


+ 17
- 16
IPA.Loader/Config/Stores/CollectionConverter.cs View File

@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -14,7 +15,7 @@ namespace IPA.Config.Stores.Converters
/// <typeparam name="T">the type of the items in the collection</typeparam>
/// <typeparam name="TCollection">the instantiated type of collection</typeparam>
public class CollectionConverter<T, TCollection> : ValueConverter<TCollection>
where TCollection : ICollection<T>
where TCollection : ICollection<T?>
{
/// <summary>
/// Creates a <see cref="CollectionConverter{T, TCollection}"/> using the default converter for the
@ -54,7 +55,7 @@ namespace IPA.Config.Stores.Converters
/// <seealso cref="ValueConverter{T}.FromValue(Value, object)"/>
protected void PopulateFromValue(TCollection col, List list, object parent)
{
//Logger.log.Debug($"CollectionConverter<{typeof(T)}, {typeof(TCollection)}>({BaseConverter.GetType()}).PopulateFromValue([object], {list}, {parent.GetType()})");
if (list is null) throw new ArgumentNullException(nameof(list));
foreach (var it in list)
col.Add(BaseConverter.FromValue(it, parent));
}
@ -66,9 +67,9 @@ namespace IPA.Config.Stores.Converters
/// <param name="parent">the object that will own the resulting <typeparamref name="TCollection"/></param>
/// <returns>a new <typeparamref name="TCollection"/> holding the deserialized content of <paramref name="value"/></returns>
/// <seealso cref="ValueConverter{T}.FromValue(Value, object)"/>
public override TCollection FromValue(Value value, object parent)
public override TCollection FromValue(Value? value, object parent)
{
if (!(value is List list)) throw new ArgumentException("Argument not a List", nameof(value));
if (value is not List list) throw new ArgumentException("Argument not a List", nameof(value));
var col = Create(list.Count, parent);
PopulateFromValue(col, list, parent);
@ -81,7 +82,7 @@ namespace IPA.Config.Stores.Converters
/// <param name="parent">the object owning <paramref name="obj"/></param>
/// <returns>the <see cref="List"/> that <paramref name="obj"/> was serialized into</returns>
/// <seealso cref="ValueConverter{T}.ToValue(T, object)"/>
public override Value ToValue(TCollection obj, object parent)
public override Value? ToValue(TCollection? obj, object parent)
=> Value.From(obj.Select(t => BaseConverter.ToValue(t, parent)));
}
/// <summary>
@ -92,7 +93,7 @@ namespace IPA.Config.Stores.Converters
/// <typeparam name="TConverter">the type of the converter to use for <typeparamref name="T"/></typeparam>
/// <seealso cref="CollectionConverter{T, TCollection}"/>
public sealed class CollectionConverter<T, TCollection, TConverter> : CollectionConverter<T, TCollection>
where TCollection : ICollection<T>
where TCollection : ICollection<T?>
where TConverter : ValueConverter<T>, new()
{
/// <summary>
@ -110,7 +111,7 @@ namespace IPA.Config.Stores.Converters
/// </summary>
/// <typeparam name="T">the element type of the <see cref="ISet{T}"/></typeparam>
/// <seealso cref="CollectionConverter{T, TCollection}"/>
public class ISetConverter<T> : CollectionConverter<T, ISet<T>>
public class ISetConverter<T> : CollectionConverter<T, ISet<T?>>
{
/// <summary>
/// Creates an <see cref="ISetConverter{T}"/> using the default converter for <typeparamref name="T"/>.
@ -128,8 +129,8 @@ namespace IPA.Config.Stores.Converters
/// <param name="size">the size to initialize it to</param>
/// <param name="parent">the object that will own the new object</param>
/// <returns>the new <see cref="ISet{T}"/></returns>
protected override ISet<T> Create(int size, object parent)
=> new HashSet<T>();
protected override ISet<T?> Create(int size, object parent)
=> new HashSet<T?>();
}
/// <summary>
/// An <see cref="ISetConverter{T}"/> which default constructs a converter for use as the value converter.
@ -155,7 +156,7 @@ namespace IPA.Config.Stores.Converters
/// </summary>
/// <typeparam name="T">the element type of the <see cref="List{T}"/></typeparam>
/// <seealso cref="CollectionConverter{T, TCollection}"/>
public class ListConverter<T> : CollectionConverter<T, List<T>>
public class ListConverter<T> : CollectionConverter<T, List<T?>>
{
/// <summary>
/// Creates an <see cref="ListConverter{T}"/> using the default converter for <typeparamref name="T"/>.
@ -173,8 +174,8 @@ namespace IPA.Config.Stores.Converters
/// <param name="size">the size to initialize it to</param>
/// <param name="parent">the object that will own the new object</param>
/// <returns>the new <see cref="List{T}"/></returns>
protected override List<T> Create(int size, object parent)
=> new List<T>(size);
protected override List<T?> Create(int size, object parent)
=> new(size);
}
/// <summary>
/// A <see cref="ListConverter{T}"/> which default constructs a converter for use as the value converter.
@ -199,7 +200,7 @@ namespace IPA.Config.Stores.Converters
/// </summary>
/// <typeparam name="T">the element type of the <see cref="IList{T}"/></typeparam>
/// <seealso cref="CollectionConverter{T, TCollection}"/>
public class IListConverter<T> : CollectionConverter<T, IList<T>>
public class IListConverter<T> : CollectionConverter<T, IList<T?>>
{
/// <summary>
/// Creates an <see cref="IListConverter{T}"/> using the default converter for <typeparamref name="T"/>.
@ -217,8 +218,8 @@ namespace IPA.Config.Stores.Converters
/// <param name="size">the size to initialize it to</param>
/// <param name="parent">the object that will own the new object</param>
/// <returns>the new <see cref="IList{T}"/></returns>
protected override IList<T> Create(int size, object parent)
=> new List<T>(size);
protected override IList<T?> Create(int size, object parent)
=> new List<T?>(size);
}
/// <summary>
/// An <see cref="IListConverter{T}"/> which default constructs a converter for use as the value converter.


+ 75
- 74
IPA.Loader/Config/Stores/Converters.cs View File

@ -1,4 +1,5 @@
using IPA.Config.Data;
#nullable enable
using IPA.Config.Data;
using IPA.Config.Stores.Attributes;
using IPA.Logging;
using System;
@ -22,7 +23,7 @@ namespace IPA.Config.Stores.Converters
/// </summary>
/// <param name="val">the <see cref="Value"/> to get the integral value of</param>
/// <returns>the integral value of <paramref name="val"/>, or <see langword="null"/></returns>
public static long? IntValue(Value val)
public static long? IntValue(Value? val)
=> val is Integer inte ? inte.Value :
val is FloatingPoint fp ? fp.AsInteger()?.Value :
null;
@ -32,7 +33,7 @@ namespace IPA.Config.Stores.Converters
/// </summary>
/// <param name="val">the <see cref="Value"/> to get the floaing point value of</param>
/// <returns>the floaing point value of <paramref name="val"/>, or <see langword="null"/></returns>
public static decimal? FloatValue(Value val)
public static decimal? FloatValue(Value? val)
=> val is FloatingPoint fp ? fp.Value :
val is Integer inte ? inte.AsFloat()?.Value :
null;
@ -83,7 +84,7 @@ namespace IPA.Config.Stores.Converters
}
//Logger.log.Debug($"gives converter for value type {t}");
var valConv = Activator.CreateInstance(typeof(ValConv<>).MakeGenericType(t)) as IValConv;
var valConv = (IValConv)Activator.CreateInstance(typeof(ValConv<>).MakeGenericType(t));
return valConv.Get();
}
@ -117,7 +118,7 @@ namespace IPA.Config.Stores.Converters
IValConv<DateTime>, IValConv<DateTimeOffset>,
IValConv<TimeSpan>
{
internal static readonly ValConvImpls Impl = new ValConvImpls();
internal static readonly ValConvImpls Impl = new();
Type IValConv<char>.Get() => typeof(CharConverter);
Type IValConv<long>.Get() => typeof(LongConverter);
Type IValConv<ulong>.Get() => typeof(ULongConverter);
@ -145,7 +146,7 @@ namespace IPA.Config.Stores.Converters
/// <typeparam name="T">the type of the <see cref="ValueConverter{T}"/> that this works on</typeparam>
public static class Converter<T>
{
private static ValueConverter<T> defaultConverter = null;
private static ValueConverter<T>? defaultConverter;
/// <summary>
/// Gets the default <see cref="ValueConverter{T}"/> for the current type.
/// </summary>
@ -158,7 +159,7 @@ namespace IPA.Config.Stores.Converters
//Logger.log.Debug($"Converter<{t}>.MakeDefault()");
static ValueConverter<T> MakeInstOf(Type ty)
=> Activator.CreateInstance(ty) as ValueConverter<T>;
=> (ValueConverter<T>)Activator.CreateInstance(ty);
return MakeInstOf(Converter.GetDefaultConverterType(t));
}
@ -193,16 +194,16 @@ namespace IPA.Config.Stores.Converters
/// <param name="value">the <see cref="Value"/> tree to convert</param>
/// <param name="parent">the object which will own the created object</param>
/// <returns>the object represented by <paramref name="value"/></returns>
public override T? FromValue(Value value, object parent)
=> value == null ? null : new T?(baseConverter.FromValue(value, parent));
public override T? FromValue(Value? value, object parent)
=> value is null ? null : new T?(baseConverter.FromValue(value, parent));
/// <summary>
/// Converts a nullable <typeparamref name="T"/> to a <see cref="Value"/> tree.
/// </summary>
/// <param name="obj">the value to serialize</param>
/// <param name="parent">the object which owns <paramref name="obj"/></param>
/// <returns>a <see cref="Value"/> tree representing <paramref name="obj"/>.</returns>
public override Value ToValue(T? obj, object parent)
=> obj == null ? null : baseConverter.ToValue(obj.Value, parent);
public override Value? ToValue(T? obj, object parent)
=> obj is null ? null : baseConverter.ToValue(obj.Value, parent);
}
/// <summary>
@ -237,7 +238,7 @@ namespace IPA.Config.Stores.Converters
/// <param name="parent">the object which will own the created object</param>
/// <returns>the deserialized enum value</returns>
/// <exception cref="ArgumentException">if <paramref name="value"/> is not a <see cref="Text"/> node</exception>
public override T FromValue(Value value, object parent)
public override T FromValue(Value? value, object parent)
=> value is Text t
? (T)Enum.Parse(typeof(T), t.Value)
: throw new ArgumentException("Value not a string", nameof(value));
@ -248,8 +249,8 @@ namespace IPA.Config.Stores.Converters
/// <param name="obj">the value to serialize</param>
/// <param name="parent">the object which owns <paramref name="obj"/></param>
/// <returns>a <see cref="Text"/> node representing <paramref name="obj"/></returns>
public override Value ToValue(T obj, object parent)
=> Value.Text(obj.ToString());
public override Value? ToValue(T? obj, object parent)
=> Value.Text(obj?.ToString());
}
/// <summary>
@ -267,7 +268,7 @@ namespace IPA.Config.Stores.Converters
/// <param name="parent">the object which will own the created object</param>
/// <returns>the deserialized enum value</returns>
/// <exception cref="ArgumentException">if <paramref name="value"/> is not a <see cref="Text"/> node</exception>
public override T FromValue(Value value, object parent)
public override T FromValue(Value? value, object parent)
=> value is Text t
? (T)Enum.Parse(typeof(T), t.Value, true)
: throw new ArgumentException("Value not a string", nameof(value));
@ -278,8 +279,8 @@ namespace IPA.Config.Stores.Converters
/// <param name="obj">the value to serialize</param>
/// <param name="parent">the object which owns <paramref name="obj"/></param>
/// <returns>a <see cref="Text"/> node representing <paramref name="obj"/></returns>
public override Value ToValue(T obj, object parent)
=> Value.Text(obj.ToString());
public override Value? ToValue(T? obj, object parent)
=> Value.Text(obj?.ToString());
}
/// <summary>
@ -296,7 +297,7 @@ namespace IPA.Config.Stores.Converters
/// <param name="parent">the object which will own the created object</param>
/// <returns>the deserialized enum value</returns>
/// <exception cref="ArgumentException">if <paramref name="value"/> is not a numeric node</exception>
public override T FromValue(Value value, object parent)
public override T FromValue(Value? value, object parent)
=> (T)Enum.ToObject(typeof(T), Converter.IntValue(value)
?? throw new ArgumentException("Value not a numeric node", nameof(value)));
@ -306,7 +307,7 @@ namespace IPA.Config.Stores.Converters
/// <param name="obj">the value to serialize</param>
/// <param name="parent">the object which owns <paramref name="obj"/></param>
/// <returns>an <see cref="Integer"/> node representing <paramref name="obj"/></returns>
public override Value ToValue(T obj, object parent)
public override Value ToValue(T? obj, object parent)
=> Value.Integer(Convert.ToInt64(obj));
}
@ -314,7 +315,7 @@ namespace IPA.Config.Stores.Converters
/// A converter for instances of <see cref="IDictionary{TKey, TValue}"/>.
/// </summary>
/// <typeparam name="TValue">the value type of the dictionary</typeparam>
public class IDictionaryConverter<TValue> : ValueConverter<IDictionary<string, TValue>>
public class IDictionaryConverter<TValue> : ValueConverter<IDictionary<string, TValue?>>
{
/// <summary>
/// Gets the converter for the dictionary's value type.
@ -338,9 +339,9 @@ namespace IPA.Config.Stores.Converters
/// <param name="value">the <see cref="Map"/> to convert</param>
/// <param name="parent">the parent that will own the resulting object</param>
/// <returns>the deserialized dictionary</returns>
public override IDictionary<string, TValue> FromValue(Value value, object parent)
=> (value as Map)?.Select(kvp => (kvp.Key, val: BaseConverter.FromValue(kvp.Value, parent)))
?.ToDictionary(p => p.Key, p => p.val)
public override IDictionary<string, TValue?> FromValue(Value? value, object parent)
=> ((value as Map)?.Select(kvp => (kvp.Key, val: BaseConverter.FromValue(kvp.Value, parent)))
?.ToDictionary(p => p.Key, p => p.val))
?? throw new ArgumentException("Value not a map", nameof(value));
/// <summary>
@ -349,8 +350,8 @@ namespace IPA.Config.Stores.Converters
/// <param name="obj">the dictionary to serialize</param>
/// <param name="parent">the object that owns the dictionary</param>
/// <returns>the dictionary serialized as a <see cref="Map"/></returns>
public override Value ToValue(IDictionary<string, TValue> obj, object parent)
=> Value.From(obj.Select(p => new KeyValuePair<string, Value>(p.Key, BaseConverter.ToValue(p.Value, parent))));
public override Value? ToValue(IDictionary<string, TValue?>? obj, object parent)
=> Value.From(obj.Select(p => new KeyValuePair<string, Value?>(p.Key, BaseConverter.ToValue(p.Value, parent))));
}
/// <summary>
@ -373,7 +374,7 @@ namespace IPA.Config.Stores.Converters
/// A converter for instances of <see cref="Dictionary{TKey, TValue}"/>.
/// </summary>
/// <typeparam name="TValue">the value type of the dictionary</typeparam>
public class DictionaryConverter<TValue> : ValueConverter<Dictionary<string, TValue>>
public class DictionaryConverter<TValue> : ValueConverter<Dictionary<string, TValue?>>
{
/// <summary>
/// Gets the converter for the dictionary's value type.
@ -397,7 +398,7 @@ namespace IPA.Config.Stores.Converters
/// <param name="value">the <see cref="Map"/> to convert</param>
/// <param name="parent">the parent that will own the resulting object</param>
/// <returns>the deserialized dictionary</returns>
public override Dictionary<string, TValue> FromValue(Value value, object parent)
public override Dictionary<string, TValue?> FromValue(Value? value, object parent)
=> (value as Map)?.Select(kvp => (kvp.Key, val: BaseConverter.FromValue(kvp.Value, parent)))
?.ToDictionary(p => p.Key, p => p.val)
?? throw new ArgumentException("Value not a map", nameof(value));
@ -408,8 +409,8 @@ namespace IPA.Config.Stores.Converters
/// <param name="obj">the dictionary to serialize</param>
/// <param name="parent">the object that owns the dictionary</param>
/// <returns>the dictionary serialized as a <see cref="Map"/></returns>
public override Value ToValue(Dictionary<string, TValue> obj, object parent)
=> Value.From(obj.Select(p => new KeyValuePair<string, Value>(p.Key, BaseConverter.ToValue(p.Value, parent))));
public override Value? ToValue(Dictionary<string, TValue?>? obj, object parent)
=> Value.From(obj?.Select(p => new KeyValuePair<string, Value?>(p.Key, BaseConverter.ToValue(p.Value, parent))));
}
/// <summary>
@ -433,7 +434,7 @@ namespace IPA.Config.Stores.Converters
/// A converter for instances of <see cref="IReadOnlyDictionary{TKey, TValue}"/>.
/// </summary>
/// <typeparam name="TValue">the value type of the dictionary</typeparam>
public class IReadOnlyDictionaryConverter<TValue> : ValueConverter<IReadOnlyDictionary<string, TValue>>
public class IReadOnlyDictionaryConverter<TValue> : ValueConverter<IReadOnlyDictionary<string, TValue?>>
{
/// <summary>
/// Gets the converter for the dictionary's value type.
@ -457,7 +458,7 @@ namespace IPA.Config.Stores.Converters
/// <param name="value">the <see cref="Map"/> to convert</param>
/// <param name="parent">the parent that will own the resulting object</param>
/// <returns>the deserialized dictionary</returns>
public override IReadOnlyDictionary<string, TValue> FromValue(Value value, object parent)
public override IReadOnlyDictionary<string, TValue?> FromValue(Value? value, object parent)
=> (value as Map)?.Select(kvp => (kvp.Key, val: BaseConverter.FromValue(kvp.Value, parent)))
?.ToDictionary(p => p.Key, p => p.val)
?? throw new ArgumentException("Value not a map", nameof(value));
@ -468,8 +469,8 @@ namespace IPA.Config.Stores.Converters
/// <param name="obj">the dictionary to serialize</param>
/// <param name="parent">the object that owns the dictionary</param>
/// <returns>the dictionary serialized as a <see cref="Map"/></returns>
public override Value ToValue(IReadOnlyDictionary<string, TValue> obj, object parent)
=> Value.From(obj.Select(p => new KeyValuePair<string, Value>(p.Key, BaseConverter.ToValue(p.Value, parent))));
public override Value? ToValue(IReadOnlyDictionary<string, TValue?>? obj, object parent)
=> Value.From(obj?.Select(p => new KeyValuePair<string, Value?>(p.Key, BaseConverter.ToValue(p.Value, parent))));
}
/// <summary>
@ -500,7 +501,7 @@ namespace IPA.Config.Stores.Converters
/// <param name="parent">the object which will own the created object</param>
/// <returns>the deserialized Color object</returns>
/// <exception cref="ArgumentException">if <paramref name="value"/> is not a <see cref="Text"/> node or couldn't be parsed into a Color object</exception>
public override Color FromValue(Value value, object parent)
public override Color FromValue(Value? value, object parent)
{
if (value is Text t)
{
@ -526,146 +527,146 @@ namespace IPA.Config.Stores.Converters
internal class StringConverter : ValueConverter<string>
{
public override string FromValue(Value value, object parent)
public override string? FromValue(Value? value, object parent)
=> (value as Text)?.Value;
public override Value ToValue(string obj, object parent)
public override Value? ToValue(string? obj, object parent)
=> Value.From(obj);
}
internal class CharConverter : ValueConverter<char>
{
public override char FromValue(Value value, object parent)
public override char FromValue(Value? value, object parent)
=> (value as Text)?.Value[0]
?? throw new ArgumentException("Value not a text node", nameof(value)); // can throw nullptr
public override Value ToValue(char obj, object parent)
public override Value? ToValue(char obj, object parent)
=> Value.From(char.ToString(obj));
}
internal class LongConverter : ValueConverter<long>
{
public override long FromValue(Value value, object parent)
public override long FromValue(Value? value, object parent)
=> Converter.IntValue(value)
?? throw new ArgumentException("Value not a numeric value", nameof(value));
public override Value ToValue(long obj, object parent)
public override Value? ToValue(long obj, object parent)
=> Value.From(obj);
}
internal class ULongConverter : ValueConverter<ulong>
{
public override ulong FromValue(Value value, object parent)
public override ulong FromValue(Value? value, object parent)
=> (ulong)(Converter.FloatValue(value)
?? throw new ArgumentException("Value not a numeric value", nameof(value)));
public override Value ToValue(ulong obj, object parent)
public override Value? ToValue(ulong obj, object parent)
=> Value.From(obj);
}
internal class IntPtrConverter : ValueConverter<IntPtr>
{
public override IntPtr FromValue(Value value, object parent)
public override IntPtr FromValue(Value? value, object parent)
=> (IntPtr)Converter<long>.Default.FromValue(value, parent);
public override Value ToValue(IntPtr obj, object parent)
public override Value? ToValue(IntPtr obj, object parent)
=> Value.From((long)obj);
}
internal class UIntPtrConverter : ValueConverter<UIntPtr>
{
public override UIntPtr FromValue(Value value, object parent)
public override UIntPtr FromValue(Value? value, object parent)
=> (UIntPtr)Converter<ulong>.Default.FromValue(value, parent);
public override Value ToValue(UIntPtr obj, object parent)
public override Value? ToValue(UIntPtr obj, object parent)
=> Value.From((decimal)obj);
}
internal class IntConverter : ValueConverter<int>
{
public override int FromValue(Value value, object parent)
public override int FromValue(Value? value, object parent)
=> (int)Converter<long>.Default.FromValue(value, parent);
public override Value ToValue(int obj, object parent)
public override Value? ToValue(int obj, object parent)
=> Value.From(obj);
}
internal class UIntConverter : ValueConverter<uint>
{
public override uint FromValue(Value value, object parent)
public override uint FromValue(Value? value, object parent)
=> (uint)Converter<long>.Default.FromValue(value, parent);
public override Value ToValue(uint obj, object parent)
public override Value? ToValue(uint obj, object parent)
=> Value.From(obj);
}
internal class ShortConverter : ValueConverter<short>
{
public override short FromValue(Value value, object parent)
public override short FromValue(Value? value, object parent)
=> (short)Converter<long>.Default.FromValue(value, parent);
public override Value ToValue(short obj, object parent)
public override Value? ToValue(short obj, object parent)
=> Value.From(obj);
}
internal class UShortConverter : ValueConverter<ushort>
{
public override ushort FromValue(Value value, object parent)
public override ushort FromValue(Value? value, object parent)
=> (ushort)Converter<long>.Default.FromValue(value, parent);
public override Value ToValue(ushort obj, object parent)
public override Value? ToValue(ushort obj, object parent)
=> Value.From(obj);
}
internal class ByteConverter : ValueConverter<byte>
{
public override byte FromValue(Value value, object parent)
public override byte FromValue(Value? value, object parent)
=> (byte)Converter<long>.Default.FromValue(value, parent);
public override Value ToValue(byte obj, object parent)
public override Value? ToValue(byte obj, object parent)
=> Value.From(obj);
}
internal class SByteConverter : ValueConverter<sbyte>
{
public override sbyte FromValue(Value value, object parent)
public override sbyte FromValue(Value? value, object parent)
=> (sbyte)Converter<long>.Default.FromValue(value, parent);
public override Value ToValue(sbyte obj, object parent)
public override Value? ToValue(sbyte obj, object parent)
=> Value.From(obj);
}
internal class DecimalConverter : ValueConverter<decimal>
{
public override decimal FromValue(Value value, object parent)
public override decimal FromValue(Value? value, object parent)
=> Converter.FloatValue(value) ?? throw new ArgumentException("Value not a numeric value", nameof(value));
public override Value ToValue(decimal obj, object parent)
public override Value? ToValue(decimal obj, object parent)
=> Value.From(obj);
}
internal class FloatConverter : ValueConverter<float>
{
public override float FromValue(Value value, object parent)
public override float FromValue(Value? value, object parent)
=> (float)Converter<decimal>.Default.FromValue(value, parent);
public override Value ToValue(float obj, object parent)
public override Value? ToValue(float obj, object parent)
=> Value.From((decimal)obj);
}
internal class DoubleConverter : ValueConverter<double>
{
public override double FromValue(Value value, object parent)
public override double FromValue(Value? value, object parent)
=> (double)Converter<decimal>.Default.FromValue(value, parent);
public override Value ToValue(double obj, object parent)
public override Value? ToValue(double obj, object parent)
=> Value.From((decimal)obj);
}
internal class BooleanConverter : ValueConverter<bool>
{
public override bool FromValue(Value value, object parent)
public override bool FromValue(Value? value, object parent)
=> (value as Boolean)?.Value ?? throw new ArgumentException("Value not a Boolean", nameof(value));
public override Value ToValue(bool obj, object parent)
public override Value? ToValue(bool obj, object parent)
=> Value.From(obj);
}
internal class DateTimeConverter : ValueConverter<DateTime>
{
public override DateTime FromValue(Value value, object parent)
public override DateTime FromValue(Value? value, object parent)
{
if (!(value is Text text))
if (value is not Text text)
{
throw new ArgumentException("Value is not of type Text", nameof(value));
}
@ -678,14 +679,14 @@ namespace IPA.Config.Stores.Converters
throw new ArgumentException($"Parsing failed, {text.Value}");
}
public override Value ToValue(DateTime obj, object parent) => Value.Text(obj.ToString("O"));
public override Value? ToValue(DateTime obj, object parent) => Value.Text(obj.ToString("O"));
}
internal class DateTimeOffsetConverter : ValueConverter<DateTimeOffset>
{
public override DateTimeOffset FromValue(Value value, object parent)
public override DateTimeOffset FromValue(Value? value, object parent)
{
if (!(value is Text text))
if (value is not Text text)
{
throw new ArgumentException("Value is not of type Text", nameof(value));
}
@ -703,9 +704,9 @@ namespace IPA.Config.Stores.Converters
internal class TimeSpanConverter : ValueConverter<TimeSpan>
{
public override TimeSpan FromValue(Value value, object parent)
public override TimeSpan FromValue(Value? value, object parent)
{
if (!(value is Text text))
if (value is not Text text)
{
throw new ArgumentException("Value is not of type Text", nameof(value));
}
@ -718,6 +719,6 @@ namespace IPA.Config.Stores.Converters
throw new ArgumentException($"Parsing failed, {text.Value}");
}
public override Value ToValue(TimeSpan obj, object parent) => Value.Text(obj.ToString());
public override Value? ToValue(TimeSpan obj, object parent) => Value.Text(obj.ToString());
}
}

+ 17
- 14
IPA.Loader/Config/Stores/CustomObjectConverter.cs View File

@ -1,4 +1,5 @@
using IPA.Config.Data;
#nullable enable
using IPA.Config.Data;
using System;
namespace IPA.Config.Stores.Converters
@ -12,24 +13,26 @@ namespace IPA.Config.Stores.Converters
{
private interface IImpl
{
T FromValue(Value value, object parent);
Value ToValue(T obj, object parent);
T? FromValue(Value? value, object parent);
Value? ToValue(T? obj, object parent);
}
private class Impl<U> : IImpl where U : class, GeneratedStoreImpl.IGeneratedStore<T>, T
{
private static readonly GeneratedStoreImpl.GeneratedStoreCreator creator = GeneratedStoreImpl.GetCreator(typeof(T));
private static U Create(GeneratedStoreImpl.IGeneratedStore parent)
=> creator(parent) as U;
private static U Create(GeneratedStoreImpl.IGeneratedStore? parent)
=> (U)creator(parent);
public T FromValue(Value value, object parent)
public T? FromValue(Value? value, object parent)
{ // lots of casting here, but it works i promise (probably) (parent can be a non-IGeneratedStore, however it won't necessarily behave then)
if (value is null) return null;
var obj = Create(parent as GeneratedStoreImpl.IGeneratedStore);
obj.Deserialize(value);
return obj;
}
public Value ToValue(T obj, object parent)
public Value? ToValue(T? obj, object parent)
{
if (obj is null) return null;
if (obj is GeneratedStoreImpl.IGeneratedStore store)
return store.Serialize();
else
@ -51,7 +54,7 @@ namespace IPA.Config.Stores.Converters
/// <param name="parent">the parent object that will own the deserialized value</param>
/// <returns>the deserialized value</returns>
/// <seealso cref="ValueConverter{T}.FromValue(Value, object)"/>
public static T Deserialize(Value value, object parent)
public static T? Deserialize(Value? value, object parent)
=> impl.FromValue(value, parent);
/// <summary>
@ -61,7 +64,7 @@ namespace IPA.Config.Stores.Converters
/// <param name="parent">the parent object that owns <paramref name="obj"/></param>
/// <returns>the <see cref="Value"/> tree that represents <paramref name="obj"/></returns>
/// <seealso cref="ValueConverter{T}.ToValue(T, object)"/>
public static Value Serialize(T obj, object parent)
public static Value? Serialize(T? obj, object parent)
=> impl.ToValue(obj, parent);
/// <summary>
@ -71,7 +74,7 @@ namespace IPA.Config.Stores.Converters
/// <param name="parent">the parent object that will own the deserialized value</param>
/// <returns>the deserialized value</returns>
/// <seealso cref="ValueConverter{T}.FromValue(Value, object)"/>
public override T FromValue(Value value, object parent)
public override T? FromValue(Value? value, object parent)
=> Deserialize(value, parent);
/// <summary>
@ -81,7 +84,7 @@ namespace IPA.Config.Stores.Converters
/// <param name="parent">the parent object that owns <paramref name="obj"/></param>
/// <returns>the <see cref="Value"/> tree that represents <paramref name="obj"/></returns>
/// <seealso cref="ValueConverter{T}.ToValue(T, object)"/>
public override Value ToValue(T obj, object parent)
public override Value? ToValue(T? obj, object parent)
=> Serialize(obj, parent);
}
@ -104,7 +107,7 @@ namespace IPA.Config.Stores.Converters
/// <param name="parent">the parent object that will own the deserialized value</param>
/// <returns>the deserialized value</returns>
/// <seealso cref="ValueConverter{T}.FromValue(Value, object)"/>
public static T Deserialize(Value value, object parent)
public static T Deserialize(Value? value, object parent)
=> deserialize(value, parent);
/// <summary>
@ -123,7 +126,7 @@ namespace IPA.Config.Stores.Converters
/// <param name="parent">the parent object that will own the deserialized value</param>
/// <returns>the deserialized value</returns>
/// <seealso cref="ValueConverter{T}.FromValue(Value, object)"/>
public override T FromValue(Value value, object parent)
public override T FromValue(Value? value, object parent)
=> Deserialize(value, parent);
/// <summary>
@ -133,7 +136,7 @@ namespace IPA.Config.Stores.Converters
/// <param name="parent">the parent object that owns <paramref name="obj"/></param>
/// <returns>the <see cref="Value"/> tree that represents <paramref name="obj"/></returns>
/// <seealso cref="ValueConverter{T}.ToValue(T, object)"/>
public override Value ToValue(T obj, object parent)
public override Value? ToValue(T obj, object parent)
=> Serialize(obj);
}


+ 5
- 4
IPA.Loader/Config/Stores/GeneratedStoreImpl/ConversionDelegates.cs View File

@ -1,4 +1,5 @@
using IPA.Config.Data;
#nullable enable
using IPA.Config.Data;
using System;
using System.Collections.Generic;
using System.Linq;
@ -13,12 +14,12 @@ namespace IPA.Config.Stores
{
internal delegate Value SerializeObject<T>(T obj);
internal delegate T DeserializeObject<T>(Value val, object parent);
internal delegate T DeserializeObject<T>(Value? val, object parent);
private static class DelegateStore<T>
{
public static SerializeObject<T> Serialize;
public static DeserializeObject<T> Deserialize;
public static SerializeObject<T>? Serialize;
public static DeserializeObject<T>? Deserialize;
}
internal static SerializeObject<T> GetSerializerDelegate<T>()


+ 2
- 1
IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs View File

@ -1,4 +1,5 @@
using IPA.Config.Data;
#nullable enable
using IPA.Config.Data;
using IPA.Config.Stores.Attributes;
using IPA.Logging;
using System;


+ 6
- 1
IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs View File

@ -1,4 +1,5 @@
using IPA.Config.Data;
#nullable enable
using IPA.Config.Data;
using IPA.Logging;
using System;
using System.Collections;
@ -43,6 +44,7 @@ namespace IPA.Config.Stores
private static void EmitDeserializeNullable(ILGenerator il, SerializedMemberInfo member, Type expected, LocalAllocator GetLocal,
Action<ILGenerator> thisarg, Action<ILGenerator> parentobj)
{
if (!member.IsNullable) throw new InvalidOperationException("EmitDeserializeNullable called for non-nullable!");
thisarg ??= il => il.Emit(OpCodes.Ldarg_0);
parentobj ??= thisarg;
EmitDeserializeValue(il, member, member.NullableWrappedType, expected, GetLocal, thisarg, parentobj);
@ -161,6 +163,9 @@ namespace IPA.Config.Stores
private static void EmitDeserializeConverter(ILGenerator il, SerializedMemberInfo member, Label nextLabel, LocalAllocator GetLocal,
Action<ILGenerator> thisobj, Action<ILGenerator> parentobj)
{
if (!member.HasConverter)
throw new InvalidOperationException("EmitDeserializeConverter called for member without converter");
using var stlocal = GetLocal.Allocate(typeof(Value));
using var valLocal = GetLocal.Allocate(member.Type);


+ 15