Add some code analysis for try-return with nullable out

This commit is contained in:
Kurt 2021-01-17 00:05:07 -08:00
parent bfd948ee2b
commit 680e8b711d
7 changed files with 81 additions and 29 deletions

View File

@ -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()
/// <param name="name">Property Name to check</param>
/// <param name="pi">Property Info retrieved (if any).</param>
/// <returns>True if has property, false if does not.</returns>
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);
}
/// <summary>
/// Tries to fetch the <see cref="PKM"/> property from the cache of available properties.
/// </summary>
/// <param name="type">Type to check</param>
/// <param name="name">Property Name to check</param>
/// <param name="pi">Property Info retrieved (if any).</param>
/// <returns>True if has property, false if does not.</returns>
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);
}
/// <summary>
/// Gets a list of <see cref="PKM"/> types that implement the requested <see cref="property"/>.
/// </summary>
public static IEnumerable<string> 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}";
}
}
/// <summary>
/// Gets the type of the <see cref="PKM"/> property using the saved cache of properties.
/// </summary>
@ -160,8 +195,6 @@ public static bool IsFilterMatch(IEnumerable<StringInstruction> filters, object
return false;
try
{
if (pi == null)
continue;
if (pi.IsValueEqual(obj, cmd.PropertyValue) == cmd.Evaluator)
continue;
}

View File

@ -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);

View File

@ -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)
/// <returns>Indication if Not Transferable</returns>
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;
}
}

View File

@ -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)
/// <param name="data">Binary data</param>
/// <param name="sav">Output result</param>
/// <returns>True if file object reference is valid, false if none found.</returns>
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)
/// <param name="data">Binary data</param>
/// <param name="memcard">Output result</param>
/// <returns>True if file object reference is valid, false if none found.</returns>
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)
/// <param name="ext">Format hint</param>
/// <param name="sav">Reference savefile used for PC Binary compatibility checks.</param>
/// <returns>True if file object reference is valid, false if none found.</returns>
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<byte[]> pkms, Sav
/// <param name="data">Binary data</param>
/// <param name="bv">Output result</param>
/// <returns>True if file object reference is valid, false if none found.</returns>
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)
/// <param name="mg">Output result</param>
/// <param name="ext">Format hint</param>
/// <returns>True if file object reference is valid, false if none found.</returns>
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);

View File

@ -12,10 +12,25 @@ namespace System.Runtime.CompilerServices
/// This class should not be used by developers in source code.
/// </summary>
[ExcludeFromCodeCoverage, DebuggerNonUserCode]
#endif
internal static class IsExternalInit
{
}
}
namespace System.Diagnostics.CodeAnalysis
{
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
internal sealed class NotNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
}
#pragma warning restore
#endif

View File

@ -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<TypeInfo> GetAllTypeInfo(this TypeInfo? typeInfo)
/// <param name="name">Name of the property.</param>
/// <param name="pi">Reference to the property info for the object, if it exists.</param>
/// <returns>True if has property, and false if does not have property. <see cref="pi"/> is null when returning false.</returns>
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)
{

View File

@ -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;