From 680e8b711d9eb4e30d375a0ff9a7cc18903bdf4d Mon Sep 17 00:00:00 2001 From: Kurt Date: Sun, 17 Jan 2021 00:05:07 -0800 Subject: [PATCH] Add some code analysis for try-return with nullable out --- PKHeX.Core/Editing/Bulk/BatchEditing.cs | 41 ++++++++++++++++++++++--- PKHeX.Core/PKM/PKM.cs | 3 +- PKHeX.Core/PKM/Util/PKMConverter.cs | 25 +++++++-------- PKHeX.Core/Util/FileUtil.cs | 17 +++++----- PKHeX.Core/Util/FrameworkUtil.cs | 17 +++++++++- PKHeX.Core/Util/ReflectUtil.cs | 3 +- PKHeX.WinForms/MainWindow/Main.cs | 4 +-- 7 files changed, 81 insertions(+), 29 deletions(-) diff --git a/PKHeX.Core/Editing/Bulk/BatchEditing.cs b/PKHeX.Core/Editing/Bulk/BatchEditing.cs index 693c7a9a4..4be210e95 100644 --- a/PKHeX.Core/Editing/Bulk/BatchEditing.cs +++ b/PKHeX.Core/Editing/Bulk/BatchEditing.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; @@ -68,12 +69,46 @@ private static string[][] GetPropArray() /// Property Name to check /// Property Info retrieved (if any). /// True if has property, false if does not. - public static bool TryGetHasProperty(PKM pk, string name, out PropertyInfo pi) + public static bool TryGetHasProperty(PKM pk, string name, [NotNullWhen(true)] out PropertyInfo? pi) { - var props = Props[Array.IndexOf(Types, pk.GetType())]; + var type = pk.GetType(); + return TryGetHasProperty(type, name, out pi); + } + + /// + /// Tries to fetch the property from the cache of available properties. + /// + /// Type to check + /// Property Name to check + /// Property Info retrieved (if any). + /// True if has property, false if does not. + public static bool TryGetHasProperty(Type type, string name, [NotNullWhen(true)] out PropertyInfo? pi) + { + var index = Array.IndexOf(Types, type); + if (index < 0) + { + pi = null; + return false; + } + var props = Props[index]; return props.TryGetValue(name, out pi); } + /// + /// Gets a list of types that implement the requested . + /// + public static IEnumerable GetTypesImplementing(string property) + { + for (int i = 0; i < Types.Length; i++) + { + var type = Types[i]; + var props = Props[i]; + if (!props.TryGetValue(property, out var pi)) + continue; + yield return $"{type.Name}: {pi.PropertyType.Name}"; + } + } + /// /// Gets the type of the property using the saved cache of properties. /// @@ -160,8 +195,6 @@ public static bool IsFilterMatch(IEnumerable filters, object return false; try { - if (pi == null) - continue; if (pi.IsValueEqual(obj, cmd.PropertyValue) == cmd.Evaluator) continue; } diff --git a/PKHeX.Core/PKM/PKM.cs b/PKHeX.Core/PKM/PKM.cs index 7b9f6d5ab..e7acbb60e 100644 --- a/PKHeX.Core/PKM/PKM.cs +++ b/PKHeX.Core/PKM/PKM.cs @@ -1053,7 +1053,8 @@ public void TransferPropertiesWithReflection(PKM Destination) var shared = destProperties.Intersect(srcProperties); foreach (string property in shared) { - BatchEditing.TryGetHasProperty(this, property, out var src); + if (!BatchEditing.TryGetHasProperty(this, property, out var src)) + continue; var prop = src.GetValue(this); if (prop is not byte[] && BatchEditing.TryGetHasProperty(Destination, property, out var pi)) ReflectUtil.SetValue(pi, Destination, prop); diff --git a/PKHeX.Core/PKM/Util/PKMConverter.cs b/PKHeX.Core/PKM/Util/PKMConverter.cs index f37f7bd0a..ec95ebce2 100644 --- a/PKHeX.Core/PKM/Util/PKMConverter.cs +++ b/PKHeX.Core/PKM/Util/PKMConverter.cs @@ -286,17 +286,19 @@ public static bool IsConvertibleToFormat(PKM pk, int format) // Sequential case PK1 pk1: return pk1.ConvertToPK2(); case PK2 pk2: return pk2.ConvertToPK1(); - case SK2 sk2: return sk2.ConvertToPK2(); - case CK3 ck3: return ck3.ConvertToPK3(); - case XK3 xk3: return xk3.ConvertToPK3(); case PK3 pk3: return pk3.ConvertToPK4(); - case BK4 bk4: return bk4.ConvertToPK4(); case PK4 pk4: return pk4.ConvertToPK5(); case PK5 pk5: return pk5.ConvertToPK6(); case PK6 pk6: return pk6.ConvertToPK7(); case PK7 pk7: return pk7.ConvertToPK8(); case PB7 pb7: return pb7.ConvertToPK8(); + // Side-Formats back to Mainline + case SK2 sk2: return sk2.ConvertToPK2(); + case CK3 ck3: return ck3.ConvertToPK3(); + case XK3 xk3: return xk3.ConvertToPK3(); + case BK4 bk4: return bk4.ConvertToPK4(); + // None default: comment = MsgPKMConvertFailNoMethod; @@ -312,18 +314,17 @@ public static bool IsConvertibleToFormat(PKM pk, int format) /// Indication if Not Transferable private static bool IsNotTransferable(PKM pk, out string comment) { - switch (pk.Species) + switch (pk) { + case PK4 { Species: (int)Species.Pichu } pk4 when pk4.Form != 0: + case PK6 { Species: (int)Species.Pikachu } pk6 when pk6.Form != 0: + case PB7 { Species: (int)Species.Pikachu } pika when pika.Form != 0: + case PB7 { Species: (int)Species.Eevee } eevee when eevee.Form != 0: + comment = MsgPKMConvertFailForm; + return true; default: comment = string.Empty; return false; - - case 025 when pk.Form != 0 && pk.Gen6: // Cosplay Pikachu - case 172 when pk.Form != 0 && pk.Gen4: // Spiky Eared Pichu - case 025 when pk.Form == 8 && pk.LGPE: // Buddy Pikachu - case 133 when pk.Form == 1 && pk.LGPE: // Buddy Eevee - comment = MsgPKMConvertFailForm; - return true; } } diff --git a/PKHeX.Core/Util/FileUtil.cs b/PKHeX.Core/Util/FileUtil.cs index a4ac4c02c..4ab7aa625 100644 --- a/PKHeX.Core/Util/FileUtil.cs +++ b/PKHeX.Core/Util/FileUtil.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; namespace PKHeX.Core @@ -89,7 +90,7 @@ public static int GetFileSize(string path) #pragma warning restore CA1031 // Do not catch general exception types } - private static bool TryGetGP1(byte[] data, out GP1? gp1) + private static bool TryGetGP1(byte[] data, [NotNullWhen(true)] out GP1? gp1) { gp1 = null; if (data.Length != GP1.SIZE || BitConverter.ToUInt32(data, 0x28) == 0) @@ -98,7 +99,7 @@ private static bool TryGetGP1(byte[] data, out GP1? gp1) return true; } - private static bool TryGetBundle(byte[] data, out IPokeGroup? result) + private static bool TryGetBundle(byte[] data, [NotNullWhen(true)] out IPokeGroup? result) { result = null; if (RentalTeam8.IsRentalTeam(data)) @@ -138,7 +139,7 @@ public static bool IsFileTooBig(long length) /// Binary data /// Output result /// True if file object reference is valid, false if none found. - public static bool TryGetSAV(byte[] data, out SaveFile? sav) + public static bool TryGetSAV(byte[] data, [NotNullWhen(true)] out SaveFile? sav) { sav = SaveUtil.GetVariantSAV(data); return sav != null; @@ -150,7 +151,7 @@ public static bool TryGetSAV(byte[] data, out SaveFile? sav) /// Binary data /// Output result /// True if file object reference is valid, false if none found. - public static bool TryGetMemoryCard(byte[] data, out SAV3GCMemoryCard? memcard) + public static bool TryGetMemoryCard(byte[] data, [NotNullWhen(true)] out SAV3GCMemoryCard? memcard) { if (!SAV3GCMemoryCard.IsMemoryCardSize(data)) { @@ -169,7 +170,7 @@ public static bool TryGetMemoryCard(byte[] data, out SAV3GCMemoryCard? memcard) /// Format hint /// Reference savefile used for PC Binary compatibility checks. /// True if file object reference is valid, false if none found. - public static bool TryGetPKM(byte[] data, out PKM? pk, string ext, ITrainerInfo? sav = null) + public static bool TryGetPKM(byte[] data, [NotNullWhen(true)] out PKM? pk, string ext, ITrainerInfo? sav = null) { if (ext == ".pgt") // size collision with pk6 { @@ -211,7 +212,7 @@ public static bool TryGetPCBoxBin(byte[] data, out IEnumerable pkms, Sav /// Binary data /// Output result /// True if file object reference is valid, false if none found. - public static bool TryGetBattleVideo(byte[] data, out BattleVideo? bv) + public static bool TryGetBattleVideo(byte[] data, [NotNullWhen(true)] out BattleVideo? bv) { bv = BattleVideo.GetVariantBattleVideo(data); return bv != null; @@ -224,7 +225,7 @@ public static bool TryGetBattleVideo(byte[] data, out BattleVideo? bv) /// Output result /// Format hint /// True if file object reference is valid, false if none found. - public static bool TryGetMysteryGift(byte[] data, out MysteryGift? mg, string ext) + public static bool TryGetMysteryGift(byte[] data, [NotNullWhen(true)] out MysteryGift? mg, string ext) { mg = MysteryGift.GetMysteryGift(data, ext); return mg != null; @@ -256,7 +257,7 @@ public static string GetPKMTempFileName(PKM pk, bool encrypt) if (!fi.Exists) return null; if (fi.Length == GP1.SIZE && TryGetGP1(File.ReadAllBytes(file), out var gp1)) - return gp1?.ConvertToPB7(sav); + return gp1.ConvertToPB7(sav); if (!PKX.IsPKM(fi.Length) && !MysteryGift.IsMysteryGift(fi.Length)) return null; var data = File.ReadAllBytes(file); diff --git a/PKHeX.Core/Util/FrameworkUtil.cs b/PKHeX.Core/Util/FrameworkUtil.cs index f6069cf6d..f255af5f4 100644 --- a/PKHeX.Core/Util/FrameworkUtil.cs +++ b/PKHeX.Core/Util/FrameworkUtil.cs @@ -12,10 +12,25 @@ namespace System.Runtime.CompilerServices /// This class should not be used by developers in source code. /// [ExcludeFromCodeCoverage, DebuggerNonUserCode] -#endif internal static class IsExternalInit { } } +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class NotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } +} #pragma warning restore +#endif diff --git a/PKHeX.Core/Util/ReflectUtil.cs b/PKHeX.Core/Util/ReflectUtil.cs index c0d916050..68bfcaa95 100644 --- a/PKHeX.Core/Util/ReflectUtil.cs +++ b/PKHeX.Core/Util/ReflectUtil.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; @@ -120,7 +121,7 @@ public static IEnumerable GetAllTypeInfo(this TypeInfo? typeInfo) /// Name of the property. /// Reference to the property info for the object, if it exists. /// True if has property, and false if does not have property. is null when returning false. - public static bool HasProperty(object obj, string name, out PropertyInfo? pi) => (pi = GetPropertyInfo(obj.GetType().GetTypeInfo(), name)) != null; + public static bool HasProperty(object obj, string name, [NotNullWhen(true)] out PropertyInfo? pi) => (pi = GetPropertyInfo(obj.GetType().GetTypeInfo(), name)) != null; public static PropertyInfo? GetPropertyInfo(this TypeInfo typeInfo, string name) { diff --git a/PKHeX.WinForms/MainWindow/Main.cs b/PKHeX.WinForms/MainWindow/Main.cs index a893e9060..071a52825 100644 --- a/PKHeX.WinForms/MainWindow/Main.cs +++ b/PKHeX.WinForms/MainWindow/Main.cs @@ -971,12 +971,12 @@ private void ImportQRToTabs(string url) return; var sav = C_SAV.SAV; - if (FileUtil.TryGetPKM(input, out var pk, sav.Generation.ToString(), sav) && pk != null) + if (FileUtil.TryGetPKM(input, out var pk, sav.Generation.ToString(), sav)) { OpenPKM(pk); return; } - if (FileUtil.TryGetMysteryGift(input, out var mg, url) && mg != null) + if (FileUtil.TryGetMysteryGift(input, out var mg, url)) { OpenMysteryGift(mg, url); return;