From 4adf8611131a5d86b15f017a42a0366837d14528 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 13 Apr 2022 21:07:43 -0400 Subject: enable nullable annotations in the rest of SMAPI core (#837) --- src/SMAPI/Framework/Reflection/CacheEntry.cs | 12 ++- src/SMAPI/Framework/Reflection/Reflector.cs | 127 +++++++++++++++++---------- 2 files changed, 84 insertions(+), 55 deletions(-) (limited to 'src/SMAPI/Framework/Reflection') diff --git a/src/SMAPI/Framework/Reflection/CacheEntry.cs b/src/SMAPI/Framework/Reflection/CacheEntry.cs index 6b18d204..27f48a1f 100644 --- a/src/SMAPI/Framework/Reflection/CacheEntry.cs +++ b/src/SMAPI/Framework/Reflection/CacheEntry.cs @@ -1,5 +1,4 @@ -#nullable disable - +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace StardewModdingAPI.Framework.Reflection @@ -11,21 +10,20 @@ namespace StardewModdingAPI.Framework.Reflection ** Accessors *********/ /// Whether the lookup found a valid match. - public bool IsValid { get; } + [MemberNotNullWhen(true, nameof(CacheEntry.MemberInfo))] + public bool IsValid => this.MemberInfo != null; /// The reflection data for this member (or null if invalid). - public MemberInfo MemberInfo { get; } + public MemberInfo? MemberInfo { get; } /********* ** Public methods *********/ /// Construct an instance. - /// Whether the lookup found a valid match. /// The reflection data for this member (or null if invalid). - public CacheEntry(bool isValid, MemberInfo memberInfo) + public CacheEntry(MemberInfo? memberInfo) { - this.IsValid = isValid; this.MemberInfo = memberInfo; } } diff --git a/src/SMAPI/Framework/Reflection/Reflector.cs b/src/SMAPI/Framework/Reflection/Reflector.cs index c6fc2c79..3490ee97 100644 --- a/src/SMAPI/Framework/Reflection/Reflector.cs +++ b/src/SMAPI/Framework/Reflection/Reflector.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Reflection; using System.Runtime.Caching; @@ -14,7 +12,7 @@ namespace StardewModdingAPI.Framework.Reflection ** Fields *********/ /// The cached fields and methods found via reflection. - private readonly MemoryCache Cache = new(typeof(Reflector).FullName); + private readonly MemoryCache Cache = new(typeof(Reflector).FullName!); /// The sliding cache expiration time. private readonly TimeSpan SlidingCacheExpiry = TimeSpan.FromMinutes(5); @@ -30,8 +28,9 @@ namespace StardewModdingAPI.Framework.Reflection /// The field type. /// The object which has the field. /// The field name. - /// Whether to throw an exception if the field is not found. - /// Returns the field wrapper, or null if the field doesn't exist and is false. + /// Whether to throw an exception if the field isn't found. Due to limitations with nullable reference types, setting this to false will still mark the value non-nullable. + /// Returns the field wrapper, or null if is false and the field doesn't exist. + /// The target field doesn't exist, and is true. public IReflectedField GetField(object obj, string name, bool required = true) { // validate @@ -39,24 +38,26 @@ namespace StardewModdingAPI.Framework.Reflection throw new ArgumentNullException(nameof(obj), "Can't get a instance field from a null object."); // get field from hierarchy - IReflectedField field = this.GetFieldFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + IReflectedField? field = this.GetFieldFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (required && field == null) throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a '{name}' instance field."); - return field; + return field!; } /// Get a static field. /// The field type. /// The type which has the field. /// The field name. - /// Whether to throw an exception if the field is not found. + /// Whether to throw an exception if the field isn't found. Due to limitations with nullable reference types, setting this to false will still mark the value non-nullable. + /// Returns the field wrapper, or null if is false and the field doesn't exist. + /// The target field doesn't exist, and is true. public IReflectedField GetField(Type type, string name, bool required = true) { // get field from hierarchy - IReflectedField field = this.GetFieldFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public); + IReflectedField? field = this.GetFieldFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public); if (required && field == null) throw new InvalidOperationException($"The {type.FullName} object doesn't have a '{name}' static field."); - return field; + return field!; } /**** @@ -66,7 +67,9 @@ namespace StardewModdingAPI.Framework.Reflection /// The property type. /// The object which has the property. /// The property name. - /// Whether to throw an exception if the property is not found. + /// Whether to throw an exception if the property isn't found. Due to limitations with nullable reference types, setting this to false will still mark the value non-nullable. + /// Returns the property wrapper, or null if is false and the property doesn't exist. + /// The target property doesn't exist, and is true. public IReflectedProperty GetProperty(object obj, string name, bool required = true) { // validate @@ -74,24 +77,26 @@ namespace StardewModdingAPI.Framework.Reflection throw new ArgumentNullException(nameof(obj), "Can't get a instance property from a null object."); // get property from hierarchy - IReflectedProperty property = this.GetPropertyFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + IReflectedProperty? property = this.GetPropertyFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (required && property == null) throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a '{name}' instance property."); - return property; + return property!; } /// Get a static property. /// The property type. /// The type which has the property. /// The property name. - /// Whether to throw an exception if the property is not found. + /// Whether to throw an exception if the property isn't found. Due to limitations with nullable reference types, setting this to false will still mark the value non-nullable. + /// Returns the property wrapper, or null if is false and the property doesn't exist. + /// The target property doesn't exist, and is true. public IReflectedProperty GetProperty(Type type, string name, bool required = true) { // get field from hierarchy - IReflectedProperty property = this.GetPropertyFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); + IReflectedProperty? property = this.GetPropertyFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); if (required && property == null) throw new InvalidOperationException($"The {type.FullName} object doesn't have a '{name}' static property."); - return property; + return property!; } /**** @@ -99,8 +104,10 @@ namespace StardewModdingAPI.Framework.Reflection ****/ /// Get a instance method. /// The object which has the method. - /// The field name. - /// Whether to throw an exception if the field is not found. + /// The method name. + /// Whether to throw an exception if the method isn't found. Due to limitations with nullable reference types, setting this to false will still mark the value non-nullable. + /// Returns the method wrapper, or null if is false and the method doesn't exist. + /// The target method doesn't exist, and is true. public IReflectedMethod GetMethod(object obj, string name, bool required = true) { // validate @@ -108,23 +115,25 @@ namespace StardewModdingAPI.Framework.Reflection throw new ArgumentNullException(nameof(obj), "Can't get a instance method from a null object."); // get method from hierarchy - IReflectedMethod method = this.GetMethodFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + IReflectedMethod? method = this.GetMethodFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (required && method == null) throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a '{name}' instance method."); - return method; + return method!; } /// Get a static method. /// The type which has the method. - /// The field name. - /// Whether to throw an exception if the field is not found. + /// The method name. + /// Whether to throw an exception if the method isn't found. Due to limitations with nullable reference types, setting this to false will still mark the value non-nullable. + /// Returns the method wrapper, or null if is false and the method doesn't exist. + /// The target method doesn't exist, and is true. public IReflectedMethod GetMethod(Type type, string name, bool required = true) { // get method from hierarchy - IReflectedMethod method = this.GetMethodFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); + IReflectedMethod? method = this.GetMethodFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); if (required && method == null) throw new InvalidOperationException($"The {type.FullName} object doesn't have a '{name}' static method."); - return method; + return method!; } @@ -134,18 +143,25 @@ namespace StardewModdingAPI.Framework.Reflection /// Get a field from the type hierarchy. /// The expected field type. /// The type which has the field. - /// The object which has the field. + /// The object which has the field, or null for a static field. /// The field name. /// The reflection binding which flags which indicates what type of field to find. - private IReflectedField GetFieldFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags) + private IReflectedField? GetFieldFromHierarchy(Type type, object? obj, string name, BindingFlags bindingFlags) { bool isStatic = bindingFlags.HasFlag(BindingFlags.Static); - FieldInfo field = this.GetCached($"field::{isStatic}::{type.FullName}::{name}", () => + FieldInfo? field = this.GetCached($"field::{isStatic}::{type.FullName}::{name}", () => { - FieldInfo fieldInfo = null; - for (; type != null && fieldInfo == null; type = type.BaseType) - fieldInfo = type.GetField(name, bindingFlags); - return fieldInfo; + for (Type? curType = type; curType != null; curType = curType.BaseType) + { + FieldInfo? fieldInfo = type.GetField(name, bindingFlags); + if (fieldInfo != null) + { + type = curType; + return fieldInfo; + } + } + + return null; }); return field != null @@ -156,18 +172,25 @@ namespace StardewModdingAPI.Framework.Reflection /// Get a property from the type hierarchy. /// The expected property type. /// The type which has the property. - /// The object which has the property. + /// The object which has the property, or null for a static property. /// The property name. /// The reflection binding which flags which indicates what type of property to find. - private IReflectedProperty GetPropertyFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags) + private IReflectedProperty? GetPropertyFromHierarchy(Type type, object? obj, string name, BindingFlags bindingFlags) { bool isStatic = bindingFlags.HasFlag(BindingFlags.Static); - PropertyInfo property = this.GetCached($"property::{isStatic}::{type.FullName}::{name}", () => + PropertyInfo? property = this.GetCached($"property::{isStatic}::{type.FullName}::{name}", () => { - PropertyInfo propertyInfo = null; - for (; type != null && propertyInfo == null; type = type.BaseType) - propertyInfo = type.GetProperty(name, bindingFlags); - return propertyInfo; + for (Type? curType = type; curType != null; curType = curType.BaseType) + { + PropertyInfo? propertyInfo = type.GetProperty(name, bindingFlags); + if (propertyInfo != null) + { + type = curType; + return propertyInfo; + } + } + + return null; }); return property != null @@ -177,18 +200,25 @@ namespace StardewModdingAPI.Framework.Reflection /// Get a method from the type hierarchy. /// The type which has the method. - /// The object which has the method. + /// The object which has the method, or null for a static method. /// The method name. /// The reflection binding which flags which indicates what type of method to find. - private IReflectedMethod GetMethodFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags) + private IReflectedMethod? GetMethodFromHierarchy(Type type, object? obj, string name, BindingFlags bindingFlags) { bool isStatic = bindingFlags.HasFlag(BindingFlags.Static); - MethodInfo method = this.GetCached($"method::{isStatic}::{type.FullName}::{name}", () => + MethodInfo? method = this.GetCached($"method::{isStatic}::{type.FullName}::{name}", () => { - MethodInfo methodInfo = null; - for (; type != null && methodInfo == null; type = type.BaseType) - methodInfo = type.GetMethod(name, bindingFlags); - return methodInfo; + for (Type? curType = type; curType != null; curType = curType.BaseType) + { + MethodInfo? methodInfo = type.GetMethod(name, bindingFlags); + if (methodInfo != null) + { + type = curType; + return methodInfo; + } + } + + return null; }); return method != null @@ -200,7 +230,8 @@ namespace StardewModdingAPI.Framework.Reflection /// The expected type. /// The cache key. /// Fetches a new value to cache. - private TMemberInfo GetCached(string key, Func fetch) where TMemberInfo : MemberInfo + private TMemberInfo? GetCached(string key, Func fetch) + where TMemberInfo : MemberInfo { // get from cache if (this.Cache.Contains(key)) @@ -212,8 +243,8 @@ namespace StardewModdingAPI.Framework.Reflection } // fetch & cache new value - TMemberInfo result = fetch(); - CacheEntry cacheEntry = new(result != null, result); + TMemberInfo? result = fetch(); + CacheEntry cacheEntry = new(result); this.Cache.Add(key, cacheEntry, new CacheItemPolicy { SlidingExpiration = this.SlidingCacheExpiry }); return result; } -- cgit