Browse Source

Config generation now uses Nullable annotations

pull/74/head
Anairkoen Schno 3 years 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
- 11
IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.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;
@ -36,13 +37,12 @@ namespace IPA.Config.Stores
private static readonly MethodInfo CreateGParent =
typeof(GeneratedStoreImpl).GetMethod(nameof(Create), BindingFlags.NonPublic | BindingFlags.Static, null,
CallingConventions.Any, new[] { typeof(IGeneratedStore) }, Array.Empty<ParameterModifier>());
internal static T Create<T>(IGeneratedStore parent) where T : class => (T)Create(typeof(T), parent);
internal static T Create<T>(IGeneratedStore? parent) where T : class => (T)Create(typeof(T), parent);
private static IConfigStore Create(Type type, IGeneratedStore parent)
private static IConfigStore Create(Type type, IGeneratedStore? parent)
=> GetCreator(type)(parent);
private static readonly SingleCreationValueCache<Type, (GeneratedStoreCreator ctor, Type type)> generatedCreators
= new SingleCreationValueCache<Type, (GeneratedStoreCreator ctor, Type type)>();
private static readonly SingleCreationValueCache<Type, (GeneratedStoreCreator ctor, Type type)> generatedCreators = new();
private static (GeneratedStoreCreator ctor, Type type) GetCreatorAndGeneratedType(Type t)
=> generatedCreators.GetOrAdd(t, MakeCreator);
@ -55,7 +55,7 @@ namespace IPA.Config.Stores
internal const string GeneratedAssemblyName = "IPA.Config.Generated";
private static AssemblyBuilder assembly = null;
private static AssemblyBuilder? assembly;
private static AssemblyBuilder Assembly
{
get
@ -75,7 +75,7 @@ namespace IPA.Config.Stores
Assembly.Save(file);
}
private static ModuleBuilder module = null;
private static ModuleBuilder? module;
private static ModuleBuilder Module
{
get
@ -88,7 +88,7 @@ namespace IPA.Config.Stores
}
// TODO: does this need to be a SingleCreationValueCache or similar?
private static readonly Dictionary<Type, Dictionary<Type, FieldInfo>> TypeRequiredConverters = new Dictionary<Type, Dictionary<Type, FieldInfo>>();
private static readonly Dictionary<Type, Dictionary<Type, FieldInfo>> TypeRequiredConverters = new();
private static void CreateAndInitializeConvertersFor(Type type, IEnumerable<SerializedMemberInfo> structure)
{
if (!TypeRequiredConverters.TryGetValue(type, out var converters))
@ -96,7 +96,8 @@ namespace IPA.Config.Stores
var converterFieldType = Module.DefineType($"{type.FullName}<Converters>",
TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Abstract | TypeAttributes.AnsiClass); // a static class
var uniqueConverterTypes = structure.Where(m => m.HasConverter).Select(m => m.Converter).Distinct().ToArray();
var uniqueConverterTypes = structure.Where(m => m.HasConverter)
.Select(m => m.Converter).NonNull().Distinct().ToArray();
converters = new Dictionary<Type, FieldInfo>(uniqueConverterTypes.Length);
foreach (var convType in uniqueConverterTypes)
@ -122,11 +123,14 @@ namespace IPA.Config.Stores
TypeRequiredConverters.Add(type, converters);
converterFieldType.CreateType();
_ = converterFieldType.CreateType();
}
foreach (var member in structure.Where(m => m.HasConverter))
foreach (var member in structure)
{
if (!member.HasConverter) continue;
member.ConverterField = converters[member.Converter];
}
}
}
}

+ 21
- 20
IPA.Loader/Config/Stores/GeneratedStoreImpl/IGeneratedStore.cs View File

@ -1,4 +1,5 @@
using IPA.Logging;
#nullable enable
using IPA.Logging;
using System;
using System.Collections.Generic;
using System.Reflection;
@ -42,27 +43,27 @@ namespace IPA.Config.Stores
internal class Impl : IConfigStore
{
private readonly IGeneratedStore generated;
private long enteredTransactions = 0;
private long enteredTransactions;
internal static ConstructorInfo Ctor = typeof(Impl).GetConstructor(new[] { typeof(IGeneratedStore) });
public Impl(IGeneratedStore store) => generated = store;
private readonly AutoResetEvent resetEvent = new AutoResetEvent(false);
private readonly AutoResetEvent resetEvent = new(false);
public WaitHandle SyncObject => resetEvent;
public static WaitHandle ImplGetSyncObject(IGeneratedStore s) => FindImpl(s).SyncObject;
public static WaitHandle? ImplGetSyncObject(IGeneratedStore s) => FindImpl(s)?.SyncObject;
internal static MethodInfo ImplGetSyncObjectMethod = typeof(Impl).GetMethod(nameof(ImplGetSyncObject));
public ReaderWriterLockSlim WriteSyncObject { get; } = new ReaderWriterLockSlim();
public static ReaderWriterLockSlim ImplGetWriteSyncObject(IGeneratedStore s) => FindImpl(s)?.WriteSyncObject;
public ReaderWriterLockSlim WriteSyncObject { get; } = new();
public static ReaderWriterLockSlim? ImplGetWriteSyncObject(IGeneratedStore s) => FindImpl(s)?.WriteSyncObject;
internal static MethodInfo ImplGetWriteSyncObjectMethod = typeof(Impl).GetMethod(nameof(ImplGetWriteSyncObject));
internal static MethodInfo ImplSignalChangedMethod = typeof(Impl).GetMethod(nameof(ImplSignalChanged));
public static void ImplSignalChanged(IGeneratedStore s) => FindImpl(s).SignalChanged();
public static void ImplSignalChanged(IGeneratedStore s) => FindImpl(s)?.SignalChanged();
public void SignalChanged()
{
try
{
resetEvent.Set();
_ = resetEvent.Set();
}
catch (ObjectDisposedException e)
{
@ -72,11 +73,11 @@ namespace IPA.Config.Stores
}
internal static MethodInfo ImplInvokeChangedMethod = typeof(Impl).GetMethod(nameof(ImplInvokeChanged));
public static void ImplInvokeChanged(IGeneratedStore s) => FindImpl(s).InvokeChanged();
public static void ImplInvokeChanged(IGeneratedStore s) => FindImpl(s)?.InvokeChanged();
public void InvokeChanged() => generated.Changed();
internal static MethodInfo ImplTakeReadMethod = typeof(Impl).GetMethod(nameof(ImplTakeRead));
public static void ImplTakeRead(IGeneratedStore s) => FindImpl(s).TakeRead();
public static void ImplTakeRead(IGeneratedStore s) => FindImpl(s)?.TakeRead();
public void TakeRead()
{
if (!WriteSyncObject.IsWriteLockHeld)
@ -84,7 +85,7 @@ namespace IPA.Config.Stores
}
internal static MethodInfo ImplReleaseReadMethod = typeof(Impl).GetMethod(nameof(ImplReleaseRead));
public static void ImplReleaseRead(IGeneratedStore s) => FindImpl(s).ReleaseRead();
public static void ImplReleaseRead(IGeneratedStore s) => FindImpl(s)?.ReleaseRead();
public void ReleaseRead()
{
if (!WriteSyncObject.IsWriteLockHeld)
@ -92,24 +93,24 @@ namespace IPA.Config.Stores
}
internal static MethodInfo ImplTakeWriteMethod = typeof(Impl).GetMethod(nameof(ImplTakeWrite));
public static void ImplTakeWrite(IGeneratedStore s) => FindImpl(s).TakeWrite();
public static void ImplTakeWrite(IGeneratedStore s) => FindImpl(s)?.TakeWrite();
public void TakeWrite() => WriteSyncObject.EnterWriteLock();
internal static MethodInfo ImplReleaseWriteMethod = typeof(Impl).GetMethod(nameof(ImplReleaseWrite));
public static void ImplReleaseWrite(IGeneratedStore s) => FindImpl(s).ReleaseWrite();
public static void ImplReleaseWrite(IGeneratedStore s) => FindImpl(s)?.ReleaseWrite();
public void ReleaseWrite() => WriteSyncObject.ExitWriteLock();
internal static MethodInfo ImplChangeTransactionMethod = typeof(Impl).GetMethod(nameof(ImplChangeTransaction));
public static IDisposable ImplChangeTransaction(IGeneratedStore s, IDisposable nest) => FindImpl(s).ChangeTransaction(nest);
public static IDisposable? ImplChangeTransaction(IGeneratedStore s, IDisposable nest) => FindImpl(s)?.ChangeTransaction(nest);
// TODO: improve trasactionals so they don't always save in every case
public IDisposable ChangeTransaction(IDisposable nest, bool takeWrite = true)
=> GetFreeTransaction().InitWith(this, nest, takeWrite && !WriteSyncObject.IsWriteLockHeld);
private ChangeTransactionObj GetFreeTransaction()
private static ChangeTransactionObj GetFreeTransaction()
=> freeTransactionObjs.Count > 0 ? freeTransactionObjs.Pop()
: new ChangeTransactionObj();
// TODO: maybe sometimes clean this?
private static readonly Stack<ChangeTransactionObj> freeTransactionObjs = new Stack<ChangeTransactionObj>();
private static readonly Stack<ChangeTransactionObj> freeTransactionObjs = new();
private sealed class ChangeTransactionObj : IDisposable
{
@ -148,7 +149,7 @@ namespace IPA.Config.Stores
try
{
if (data.ownsWrite)
data.impl.ReleaseWrite();
data.impl?.ReleaseWrite();
}
catch
{
@ -163,14 +164,14 @@ namespace IPA.Config.Stores
~ChangeTransactionObj() => Dispose(false);
}
public static Impl FindImpl(IGeneratedStore store)
public static Impl? FindImpl(IGeneratedStore store)
{
while (store?.Parent != null) store = store.Parent; // walk to the top of the tree
return store?.Impl;
}
internal static MethodInfo ImplReadFromMethod = typeof(Impl).GetMethod(nameof(ImplReadFrom));
public static void ImplReadFrom(IGeneratedStore s, ConfigProvider provider) => FindImpl(s).ReadFrom(provider);
public static void ImplReadFrom(IGeneratedStore s, ConfigProvider provider) => FindImpl(s)?.ReadFrom(provider);
public void ReadFrom(ConfigProvider provider)
{
Logger.config.Debug($"Generated impl ReadFrom {generated.GetType()}");
@ -183,7 +184,7 @@ namespace IPA.Config.Stores
}
internal static MethodInfo ImplWriteToMethod = typeof(Impl).GetMethod(nameof(ImplWriteTo));
public static void ImplWriteTo(IGeneratedStore s, ConfigProvider provider) => FindImpl(s).WriteTo(provider);
public static void ImplWriteTo(IGeneratedStore s, ConfigProvider provider) => FindImpl(s)?.WriteTo(provider);
public void WriteTo(ConfigProvider provider)
{
Logger.config.Debug($"Generated impl WriteTo {generated.GetType()}");


+ 5
- 4
IPA.Loader/Config/Stores/GeneratedStoreImpl/MakeCreator.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;
@ -21,7 +22,7 @@ namespace IPA.Config.Stores
internal static partial class GeneratedStoreImpl
{
internal delegate IConfigStore GeneratedStoreCreator(IGeneratedStore parent);
internal delegate IConfigStore GeneratedStoreCreator(IGeneratedStore? parent);
private static void GetMethodThis(ILGenerator il) => il.Emit(OpCodes.Ldarg_0);
private static (GeneratedStoreCreator ctor, Type type) MakeCreator(Type type)
@ -109,7 +110,7 @@ namespace IPA.Config.Stores
const MethodAttributes virtualMemberMethod = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.Final;
#region INotifyPropertyChanged
MethodBuilder notifyChanged = null;
MethodBuilder? notifyChanged = null;
if (isINotifyPropertyChanged || hasNotifyAttribute)
{
// we don't actually want to notify if the base class implements it
@ -639,7 +640,7 @@ namespace IPA.Config.Stores
#region Members
foreach (var member in structure.Where(m => m.IsVirtual))
{ // IsVirtual implies !IsField
var prop = member.Member as PropertyInfo;
var prop = (PropertyInfo)member.Member;
var get = prop.GetGetMethod(true);
var set = prop.GetSetMethod(true);


+ 20
- 15
IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs View File

@ -1,10 +1,12 @@
using IPA.Config.Stores.Attributes;
#nullable enable
using IPA.Config.Stores.Attributes;
using IPA.Config.Stores.Converters;
using IPA.Logging;
using IPA.Utilities;
using IPA.Utilities.Async;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Text;
@ -20,23 +22,25 @@ namespace IPA.Config.Stores
{
private class SerializedMemberInfo
{
public string Name;
public MemberInfo Member;
public Type Type;
public string Name = null!;
public MemberInfo Member = null!;
public Type Type = null!;
public bool AllowNull;
public bool IsVirtual;
public bool IsField;
public bool IsNullable; // signifies whether this is a Nullable<T>
[MemberNotNullWhen(true, nameof(NullableWrappedType))]
public bool IsNullable { get; set; } // signifies whether this is a Nullable<T>
public bool HasConverter;
public bool IsGenericConverter; // used so we can call directly to the generic version if it is
public Type Converter;
public Type ConverterBase;
public Type ConverterTarget;
public FieldInfo ConverterField;
[MemberNotNullWhen(true, nameof(Converter), nameof(ConverterBase))]
public bool HasConverter { get; set; }
public bool IsGenericConverter { get; set; } // used so we can call directly to the generic version if it is
public Type? Converter;
public Type? ConverterBase;
public Type? ConverterTarget;
public FieldInfo? ConverterField;
// invalid for objects with IsNullable false
public Type NullableWrappedType => Nullable.GetUnderlyingType(Type);
public Type? NullableWrappedType => Nullable.GetUnderlyingType(Type);
// invalid for objects with IsNullable false
public PropertyInfo Nullable_HasValue => Type.GetProperty(nameof(Nullable<int>.HasValue));
// invalid for objects with IsNullable false
@ -73,6 +77,8 @@ namespace IPA.Config.Stores
{
if (converterAttr.UseDefaultConverterForType)
converterAttr = new UseConverterAttribute(Converter.GetDefaultConverterType(member.Type));
if (converterAttr.UseDefaultConverterForType)
throw new InvalidOperationException("How did we get here?");
member.Converter = converterAttr.ConverterType;
member.IsGenericConverter = converterAttr.IsGenericConverter;
@ -100,7 +106,7 @@ namespace IPA.Config.Stores
{
try
{
var conv = Activator.CreateInstance(converterAttr.ConverterType) as IValueConverter;
var conv = (IValueConverter)Activator.CreateInstance(converterAttr.ConverterType);
targetType = conv.Type;
}
catch
@ -128,8 +134,7 @@ namespace IPA.Config.Stores
return true;
}
private static readonly SingleCreationValueCache<Type, SerializedMemberInfo[]> objectStructureCache
= new SingleCreationValueCache<Type, SerializedMemberInfo[]>();
private static readonly SingleCreationValueCache<Type, SerializedMemberInfo[]> objectStructureCache = new();
private static IEnumerable<SerializedMemberInfo> ReadObjectMembers(Type type)
=> objectStructureCache.GetOrAdd(type, t => ReadObjectMembersInternal(type).ToArray());


+ 2
- 1
IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.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.Generic;


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

@ -1,4 +1,5 @@
using IPA.Config.Stores.Attributes;
#nullable enable
using IPA.Config.Stores.Attributes;
using System;
using System.Collections.Generic;
using System.ComponentModel;


+ 8
- 7
IPA.Loader/Config/Stores/ValueConverter.cs View File

@ -1,4 +1,5 @@
using IPA.Config.Data;
#nullable enable
using IPA.Config.Data;
using System;
namespace IPA.Config.Stores
@ -31,14 +32,14 @@ namespace IPA.Config.Stores
/// <param name="obj">the object to convert</param>
/// <param name="parent">the owning object of <paramref name="obj"/></param>
/// <returns>a representation of <paramref name="obj"/> as a <see cref="Value"/> structure</returns>
Value ToValue(object obj, object parent);
Value? ToValue(object? obj, object parent);
/// <summary>
/// Converts the given <see cref="Value"/> to the object type handled by this converter.
/// </summary>
/// <param name="value">the <see cref="Value"/> to deserialize</param>
/// <param name="parent">the object that will own the result</param>
/// <returns>the deserialized object</returns>
object FromValue(Value value, object parent);
object? FromValue(Value? value, object parent);
/// <summary>
/// Gets the type that this <see cref="IValueConverter"/> handles.
/// </summary>
@ -59,7 +60,7 @@ namespace IPA.Config.Stores
/// <param name="parent">the owning object of <paramref name="obj"/></param>
/// <returns>a representation of <paramref name="obj"/> as a <see cref="Value"/> structure</returns>
/// <seealso cref="IValueConverter.ToValue"/>
public abstract Value ToValue(T obj, object parent);
public abstract Value? ToValue(T? obj, object parent);
/// <summary>
/// Converts the given <see cref="Value"/> to the object type handled by this converter.
/// </summary>
@ -67,10 +68,10 @@ namespace IPA.Config.Stores
/// <param name="parent">the object that will own the result</param>
/// <returns>the deserialized object</returns>
/// <seealso cref="IValueConverter.FromValue"/>
public abstract T FromValue(Value value, object parent);
public abstract T? FromValue(Value? value, object parent);
Value IValueConverter.ToValue(object obj, object parent) => ToValue((T)obj, parent);
object IValueConverter.FromValue(Value value, object parent) => FromValue(value, parent);
Value? IValueConverter.ToValue(object? obj, object parent) => ToValue((T?)obj, parent);
object? IValueConverter.FromValue(Value? value, object parent) => FromValue(value, parent);
Type IValueConverter.Type => typeof(T);
}
}

+ 8
- 4
IPA.Loader/Utilities/AlmostVersion.cs View File

@ -305,15 +305,19 @@ namespace IPA.Utilities
/// <param name="value">the <see cref="Text"/> node to convert</param>
/// <param name="parent">the owner of the new object</param>
/// <returns></returns>
public override AlmostVersion FromValue(Value value, object parent)
=> new(Converter<string>.Default.FromValue(value, parent));
public override AlmostVersion? FromValue(Value? value, object parent)
=> Converter<string>.Default.FromValue(value, parent) switch
{
{ } v => new(v),
_ => null
};
/// <summary>
/// Converts an <see cref="AlmostVersion"/> to a <see cref="Text"/> node.
/// </summary>
/// <param name="obj">the <see cref="AlmostVersion"/> to convert</param>
/// <param name="parent">the parent of <paramref name="obj"/></param>
/// <returns>a <see cref="Text"/> node representing <paramref name="obj"/></returns>
public override Value ToValue(AlmostVersion obj, object parent)
=> Value.From(obj.ToString());
public override Value? ToValue(AlmostVersion? obj, object parent)
=> Value.From(obj?.ToString());
}
}

+ 0
- 10
IPA/_Attributes.cs View File

@ -1,10 +0,0 @@
#if NET472
namespace System.Diagnostics.CodeAnalysis
{
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class DoesNotReturnAttribute : Attribute
{
public DoesNotReturnAttribute() { }
}
}
#endif

Loading…
Cancel
Save