From f39da383a17b368e92fd243cf155b27ba42671f3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 13 Apr 2022 20:24:14 -0400 Subject: enable nullable annotations in SMAPI where no logic changes are needed (#837) --- .../ContentManagers/GameContentManager.cs | 34 +++++++++++++--------- .../GameContentManagerForAssetPropagation.cs | 4 +-- .../Framework/ContentManagers/IContentManager.cs | 13 +++++---- 3 files changed, 28 insertions(+), 23 deletions(-) (limited to 'src/SMAPI/Framework/ContentManagers') diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index e494559d..6469fea4 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; @@ -70,7 +69,7 @@ namespace StardewModdingAPI.Framework.ContentManagers return true; // managed asset - if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath)) + if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string? contentManagerID, out IAssetName? relativePath)) return this.Coordinator.DoesManagedAssetExist(contentManagerID, relativePath); // custom asset from a loader @@ -78,7 +77,7 @@ namespace StardewModdingAPI.Framework.ContentManagers IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.AssertAndNormalizeAssetName); AssetLoadOperation[] loaders = this.GetLoaders(info).ToArray(); - if (!this.AssertMaxOneRequiredLoader(info, loaders, out string error)) + if (!this.AssertMaxOneRequiredLoader(info, loaders, out string? error)) { this.Monitor.Log(error, LogLevel.Warn); return false; @@ -102,7 +101,7 @@ namespace StardewModdingAPI.Framework.ContentManagers return this.RawLoad(assetName, useCache: true); // get managed asset - if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath)) + if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string? contentManagerID, out IAssetName? relativePath)) { T managedAsset = this.Coordinator.LoadManagedAsset(contentManagerID, relativePath); this.TrackAsset(assetName, managedAsset, useCache); @@ -151,14 +150,15 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Load the initial asset from the registered loaders. /// The basic asset metadata. /// Returns the loaded asset metadata, or null if no loader matched. - private IAssetData ApplyLoader(IAssetInfo info) + private IAssetData? ApplyLoader(IAssetInfo info) + where T : notnull { // find matching loader - AssetLoadOperation loader; + AssetLoadOperation? loader; { AssetLoadOperation[] loaders = this.GetLoaders(info).OrderByDescending(p => p.Priority).ToArray(); - if (!this.AssertMaxOneRequiredLoader(info, loaders, out string error)) + if (!this.AssertMaxOneRequiredLoader(info, loaders, out string? error)) { this.Monitor.Log(error, LogLevel.Warn); return null; @@ -196,20 +196,21 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The basic asset metadata. /// The loaded asset. private IAssetData ApplyEditors(IAssetInfo info, IAssetData asset) + where T : notnull { IAssetData GetNewData(object data) => new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName, this.Reflection); // special case: if the asset was loaded with a more general type like 'object', call editors with the actual type instead. { Type actualType = asset.Data.GetType(); - Type actualOpenType = actualType.IsGenericType ? actualType.GetGenericTypeDefinition() : null; + Type? actualOpenType = actualType.IsGenericType ? actualType.GetGenericTypeDefinition() : null; if (typeof(T) != actualType && (actualOpenType == typeof(Dictionary<,>) || actualOpenType == typeof(List<>) || actualType == typeof(Texture2D) || actualType == typeof(Map))) { return (IAssetData)this.GetType() .GetMethod(nameof(this.ApplyEditors), BindingFlags.NonPublic | BindingFlags.Instance)! .MakeGenericMethod(actualType) - .Invoke(this, new object[] { info, asset }); + .Invoke(this, new object[] { info, asset })!; } } @@ -232,6 +233,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } // validate edit + // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- it's only guaranteed non-null after this method if (asset.Data == null) { mod.LogAsMod($"Mod incorrectly set asset '{info.Name}'{this.GetOnBehalfOfLabel(editor.OnBehalfOf)} to a null value; ignoring override.", LogLevel.Warn); @@ -252,6 +254,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The asset type. /// The basic asset metadata. private IEnumerable GetLoaders(IAssetInfo info) + where T : notnull { return this.Coordinator .GetAssetOperations(info) @@ -262,6 +265,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The asset type. /// The basic asset metadata. private IEnumerable GetEditors(IAssetInfo info) + where T : notnull { return this.Coordinator .GetAssetOperations(info) @@ -273,7 +277,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The asset loaders to apply. /// The error message to show to the user, if the method returns false. /// Returns true if only one loader will apply, else false. - private bool AssertMaxOneRequiredLoader(IAssetInfo info, AssetLoadOperation[] loaders, out string error) + private bool AssertMaxOneRequiredLoader(IAssetInfo info, AssetLoadOperation[] loaders, [NotNullWhen(false)] out string? error) { AssetLoadOperation[] required = loaders.Where(p => p.Priority == AssetLoadPriority.Exclusive).ToArray(); if (required.Length <= 1) @@ -299,7 +303,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The content pack on whose behalf the action is being performed. /// whether to format the label as a parenthetical shown after the mod name like (for the 'X' content pack), instead of a standalone label like the 'X' content pack. /// Returns the on-behalf-of label if applicable, else null. - private string GetOnBehalfOfLabel(IModMetadata onBehalfOf, bool parenthetical = true) + [return: NotNullIfNotNull("onBehalfOf")] + private string? GetOnBehalfOfLabel(IModMetadata? onBehalfOf, bool parenthetical = true) { if (onBehalfOf == null) return null; @@ -315,7 +320,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The loaded asset data. /// The loader which loaded the asset. /// Returns whether the asset passed validation checks (after any fixes were applied). - private bool TryFixAndValidateLoadedAsset(IAssetInfo info, T data, AssetLoadOperation loader) + private bool TryFixAndValidateLoadedAsset(IAssetInfo info, [NotNullWhen(true)] T? data, AssetLoadOperation loader) + where T : notnull { IModMetadata mod = loader.Mod; @@ -335,7 +341,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // add missing tilesheet if (loadedMap.GetTileSheet(vanillaSheet.Id) == null) { - mod.Monitor.LogOnce("SMAPI fixed maps loaded by this mod to prevent errors. See the log file for details.", LogLevel.Warn); + mod.Monitor!.LogOnce("SMAPI fixed maps loaded by this mod to prevent errors. See the log file for details.", LogLevel.Warn); this.Monitor.Log($"Fixed broken map replacement: {mod.DisplayName} loaded '{info.Name}' without a required tilesheet (id: {vanillaSheet.Id}, source: {vanillaSheet.ImageSource})."); loadedMap.AddTileSheet(new TileSheet(vanillaSheet.Id, loadedMap, vanillaSheet.ImageSource, vanillaSheet.SheetSize, vanillaSheet.TileSize)); diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs index 46d5d24e..1b0e1016 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Globalization; using Microsoft.Xna.Framework.Graphics; @@ -39,7 +37,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Get whether a texture was loaded by this content manager. /// The texture to check. - public bool IsResponsibleFor(Texture2D texture) + public bool IsResponsibleFor(Texture2D? texture) { return texture?.Tag is string tag diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs index c8b2ae64..ac67cad5 100644 --- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Content; @@ -33,25 +31,28 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Get whether an asset exists and can be loaded. /// The expected asset type. /// The normalized asset name. - bool DoesAssetExist(IAssetName assetName); + bool DoesAssetExist(IAssetName assetName) + where T: notnull; /// Load an asset through the content pipeline, using a localized variant of the if available. /// The type of asset to load. /// The asset name relative to the loader root directory. /// The language for which to load the asset. /// Whether to read/write the loaded asset to the asset cache. - T LoadLocalized(IAssetName assetName, LocalizedContentManager.LanguageCode language, bool useCache); + T LoadLocalized(IAssetName assetName, LocalizedContentManager.LanguageCode language, bool useCache) + where T : notnull; /// Load an asset through the content pipeline, using the exact asset name without checking for localized variants. /// The type of asset to load. /// The asset name relative to the loader root directory. /// Whether to read/write the loaded asset to the asset cache. - T LoadExact(IAssetName assetName, bool useCache); + T LoadExact(IAssetName assetName, bool useCache) + where T : notnull; /// Assert that the given key has a valid format and return a normalized form consistent with the underlying cache. /// The asset key to check. /// The asset key is empty or contains invalid characters. - string AssertAndNormalizeAssetName(string assetName); + string AssertAndNormalizeAssetName(string? assetName); /// Get the current content locale. string GetLocale(); -- cgit