diff options
-rw-r--r-- | src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs | 7 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModLoading/RewriteHelper.cs | 163 |
2 files changed, 167 insertions, 3 deletions
diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs index bd5c97d6..79db6921 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -67,7 +67,8 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders // validate return type string actualReturnTypeID = this.GetComparableTypeID(targetField.FieldType); string expectedReturnTypeID = this.GetComparableTypeID(fieldRef.FieldType); - if (actualReturnTypeID != expectedReturnTypeID) + + if (!RewriteHelper.LooksLikeSameType(expectedReturnTypeID, actualReturnTypeID)) { this.NounPhrase = $"reference to {fieldRef.DeclaringType.FullName}.{fieldRef.Name} (field returns {this.GetFriendlyTypeName(targetField.FieldType, actualReturnTypeID)}, not {this.GetFriendlyTypeName(fieldRef.FieldType, expectedReturnTypeID)})"; return InstructionHandleResult.NotCompatible; @@ -110,8 +111,8 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// <param name="type">The type reference.</param> private bool ShouldValidate(TypeReference type) { - if (type == null) - return false; + if (type != null) + return true; // Extract scope name from type string representation for compatibility // Under Linux, type.Scope.Name sometimes reports incorrectly diff --git a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs index 56a60a72..9eb8b3a5 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using Mono.Cecil; @@ -90,5 +91,167 @@ namespace StardewModdingAPI.Framework.ModLoading .GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public) .Any(method => RewriteHelper.HasMatchingSignature(method, reference)); } + + /// <summary>Determine whether this type ID has a placeholder such as !0.</summary> + /// <param name="typeID">The type to check.</param> + /// <returns>true if the type ID contains a placeholder, false if not.</returns> + public static bool HasPlaceholder(string typeID) + { + return typeID.Contains("!0"); + } + + /// <summary> returns whether this type ID is a placeholder, i.e., it begins with "!".</summary> + /// <param name="symbol">The symbol to validate.</param> + /// <returns>true if the symbol is a placeholder, false if not</returns> + public static bool IsPlaceholder(string symbol) + { + return symbol.StartsWith("!"); + } + + /// <summary>Determine whether two type IDs look like the same type, accounting for placeholder values such as !0.</summary> + /// <param name="typeA">The type ID to compare.</param> + /// <param name="typeB">The other type ID to compare.</param> + /// <returns>true if the type IDs look like the same type, false if not.</returns> + public static bool LooksLikeSameType(string typeA, string typeB) + { + string placeholderType, actualType = ""; + + if (RewriteHelper.HasPlaceholder(typeA)) + { + placeholderType = typeA; + actualType = typeB; + } else if (RewriteHelper.HasPlaceholder(typeB)) + { + placeholderType = typeB; + actualType = typeA; + } else + { + return typeA == typeB; + } + + return RewriteHelper.PlaceholderTypeValidates(placeholderType, actualType); + } + + protected class SymbolLocation + { + public string symbol; + public int depth; + + public SymbolLocation(string symbol, int depth) + { + this.symbol = symbol; + this.depth = depth; + } + } + + private static List<char> symbolBoundaries = new List<char>{'<', '>', ','}; + + /// <summary> Traverses and parses out symbols from a type which does not contain placeholder values.</summary> + /// <param name="type">The type to traverse.</param> + /// <param name="typeSymbols">A List in which to store the parsed symbols.</param> + private static void TraverseActualType(string type, List<SymbolLocation> typeSymbols) + { + int depth = 0; + string symbol = ""; + + foreach (char c in type) + { + if (RewriteHelper.symbolBoundaries.Contains(c)) + { + typeSymbols.Add(new SymbolLocation(symbol, depth)); + symbol = ""; + switch (c) { + case '<': + depth++; + break; + case '>': + depth--; + break; + default: + break; + } + } + else + symbol += c; + } + } + + /// <summary> Determines whether two symbols in a type ID match, accounting for placeholders such as !0.</summary> + /// <param name="symbolA">A symbol in a typename which contains placeholders.</param> + /// <param name="symbolB">A symbol in a typename which does not contain placeholders.</param> + /// <param name="placeholderMap">A dictionary containing a mapping of placeholders to concrete types.</param> + /// <returns>true if the symbols match, false if not.</returns> + private static bool SymbolsMatch(SymbolLocation symbolA, SymbolLocation symbolB, Dictionary<string, string> placeholderMap) + { + System.Console.Write($"comparing {symbolA.symbol} at depth {symbolA.depth} to {symbolB.symbol} at {symbolB.depth}"); + if (symbolA.depth != symbolB.depth) + return false; + + if (!RewriteHelper.IsPlaceholder(symbolA.symbol)) { + return symbolA.symbol == symbolB.symbol; + } + + if (placeholderMap.ContainsKey(symbolA.symbol)) + { + return placeholderMap[symbolA.symbol] == symbolB.symbol; + } + + placeholderMap[symbolA.symbol] = symbolB.symbol; + + return true; + } + + /// <summary> Determines whether a type which has placeholders correctly resolves to the concrete type provided. </summary> + /// <param name="type">A type containing placeholders such as !0.</param> + /// <param name="typeSymbols">The list of symbols extracted from the concrete type.</param> + /// <returns>true if the type resolves correctly, false if not.</returns> + private static bool PlaceholderTypeResolvesToActualType(string type, List<SymbolLocation> typeSymbols) + { + Dictionary<string, string> placeholderMap = new Dictionary<string, string>(); + + int depth = 0, symbolCount = 0; + string symbol = ""; + + foreach (char c in type) + { + if (symbolBoundaries.Contains(c)) + { + bool match = RewriteHelper.SymbolsMatch(new SymbolLocation(symbol, depth), typeSymbols[symbolCount], placeholderMap); + System.Console.Write($"match: {match}"); + if (typeSymbols.Count <= symbolCount || + !match) + return false; + + symbolCount++; + symbol = ""; + switch (c) + { + case '<': + depth++; + break; + case '>': + depth--; + break; + default: + break; + } + } + else + symbol += c; + } + + return true; + } + + /// <summary>Determines whether a type with placeholders in it matches a type without placeholders.</summary> + /// <param name="placeholderType">The type with placeholders in it.</param> + /// <param name="actualType">The type without placeholders.</param> + /// <returns>true if the placeholder type can resolve to the actual type, false if not.</returns> + private static bool PlaceholderTypeValidates(string placeholderType, string actualType) { + List<SymbolLocation> typeSymbols = new List<SymbolLocation>(); + + RewriteHelper.TraverseActualType(actualType, typeSymbols); + return PlaceholderTypeResolvesToActualType(placeholderType, typeSymbols); + } } } |