Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
# Gradle cache directory
.gradle/

# Jetbrains cache directory
.idea/

# Autogenerated VS/MD/Consulo solution and project files
ExportedObj/
.consulo/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static void AddTo (AdvancedDropdownItem root, IEnumerable<Type> types)
};
root.AddChild(nullItem);

Type[] typeArray = types.OrderByType().ToArray();
Type[] typeArray = types as Type[] ?? types.ToArray();

// Single namespace if the root has one namespace and the nest is unbranched.
bool isSingleNamespace = true;
Expand Down Expand Up @@ -111,7 +111,8 @@ public static void AddTo (AdvancedDropdownItem root, IEnumerable<Type> types)
}

// Add type item.
var item = new AdvancedTypePopupItem(type, ObjectNames.NicifyVariableName(splittedTypePath[splittedTypePath.Length - 1]))
var item = new AdvancedTypePopupItem(type, TypeMenuUtility.CachedNicifyVariableName(splittedTypePath[splittedTypePath.Length - 1]))

{
id = itemCount++
};
Expand Down Expand Up @@ -145,7 +146,7 @@ public AdvancedTypePopup (IEnumerable<Type> types, int maxLineCount, AdvancedDro

public void SetTypes (IEnumerable<Type> types)
{
this.types = types.ToArray();
this.types = types.OrderByType().ToArray();
}

protected override AdvancedDropdownItem BuildRoot ()
Expand All @@ -163,6 +164,6 @@ protected override void ItemSelected (AdvancedDropdownItem item)
OnItemSelected?.Invoke(typePopupItem);
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -207,13 +207,13 @@ private GUIContent GetTypeName (SerializedProperty property)
typeName = typeMenu.GetTypeNameWithoutPath();
if (!string.IsNullOrWhiteSpace(typeName))
{
typeName = ObjectNames.NicifyVariableName(typeName);
typeName = TypeMenuUtility.CachedNicifyVariableName(typeName);
}
}

if (string.IsNullOrWhiteSpace(typeName))
{
typeName = ObjectNames.NicifyVariableName(type.Name);
typeName = TypeMenuUtility.CachedNicifyVariableName(TypeMenuUtility.GetNiceGenericName(type));
}

GUIContent result = new GUIContent(typeName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,50 +10,127 @@ public static class TypeMenuUtility

public const string NullDisplayName = "<null>";

private static readonly Dictionary<Type, string[]> splittedTypePathCache = new Dictionary<Type, string[]>();
private static readonly Dictionary<Type, AddTypeMenuAttribute> attributeCache = new Dictionary<Type, AddTypeMenuAttribute>();
private static readonly Dictionary<string, string> nicifyCache = new Dictionary<string, string>();


public static AddTypeMenuAttribute GetAttribute (Type type)
{
return Attribute.GetCustomAttribute(type, typeof(AddTypeMenuAttribute)) as AddTypeMenuAttribute;
}
if (type == null)
{
return null;
}

if (attributeCache.TryGetValue(type, out AddTypeMenuAttribute cached))
{
return cached;
}

public static string[] GetSplittedTypePath (Type type)
var result = Attribute.GetCustomAttribute(type, typeof(AddTypeMenuAttribute)) as AddTypeMenuAttribute;
attributeCache.Add(type, result);
return result;
}

public static string[] GetSplittedTypePath(Type type)
{
if (splittedTypePathCache.TryGetValue(type, out string[] cached))
{
return cached;
}

string[] result;

AddTypeMenuAttribute typeMenu = GetAttribute(type);
if (typeMenu != null)
{
return typeMenu.GetSplittedMenuName();
result = typeMenu.GetSplittedMenuName();
}
else
{
int splitIndex = type.FullName.LastIndexOf('.');
string fullName = GetNiceGenericFullName(type);
int splitIndex = fullName.LastIndexOf('.');
if (splitIndex >= 0)
{
return new string[] { type.FullName.Substring(0, splitIndex), type.FullName.Substring(splitIndex + 1) };
result = new string[] { fullName.Substring(0, splitIndex), fullName.Substring(splitIndex + 1) };
}
else
{
return new string[] { type.Name };
result = new string[] { GetNiceGenericName(type) };
}
}

splittedTypePathCache.Add(type, result);
return result;
}

public static IEnumerable<Type> OrderByType (this IEnumerable<Type> source)
public static IEnumerable<Type> OrderByType(this IEnumerable<Type> source)
{
return source.OrderBy(type =>
{
if (type == null)
{
return -999;
}

return GetAttribute(type)?.Order ?? 0;
}).ThenBy(type =>
{
if (type == null)
{
return null;
}
return GetAttribute(type)?.MenuName ?? type.Name;

return GetAttribute(type)?.MenuName ?? GetNiceGenericName(type);
});
}

public static string GetNiceGenericName(Type type)
{
if (!type.IsGenericType)
{
return type.Name;
}

string baseName = type.Name;
int backtickIndex = baseName.IndexOf('`');
if (backtickIndex > 0)
{
baseName = baseName.Substring(0, backtickIndex);
}

Type[] args = type.GetGenericArguments();
string argsJoined = string.Join(", ", args.Select(a => GetNiceGenericName(a)));
return $"{baseName}<{argsJoined}>";
}

private static string GetNiceGenericFullName(Type type)
{
if (!type.IsGenericType)
{
return type.FullName ?? type.Name;
}

string ns = type.Namespace;
string niceName = GetNiceGenericName(type);
return string.IsNullOrEmpty(ns) ? niceName : $"{ns}.{niceName}";
}

public static string CachedNicifyVariableName (string name)
{
if (string.IsNullOrEmpty(name))
{
return name;
}

if (nicifyCache.TryGetValue(name, out string cached))
{
return cached;
}

string result = ObjectNames.NicifyVariableName(name);
nicifyCache.Add(name, result);
return result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public bool IsAllowed (Type candiateType)
return
(candiateType.IsPublic || candiateType.IsNestedPublic || candiateType.IsNestedPrivate) &&
!candiateType.IsAbstract &&
!candiateType.IsGenericType &&
!candiateType.ContainsGenericParameters &&
!candiateType.IsPrimitive &&
!candiateType.IsEnum &&
!typeof(UnityEngine.Object).IsAssignableFrom(candiateType) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,32 @@ private IEnumerable<Type> GetTypesWithGeneric (Type baseType)

result = new List<Type>();

IEnumerable<Type> types = EnumerateAllTypesSafely();
foreach (Type type in types)
// Prepare generic inference data upfront
bool baseIsConstructedGeneric = baseType.IsGenericType && !baseType.IsGenericTypeDefinition && !baseType.ContainsGenericParameters;
Type baseGenericDef = baseIsConstructedGeneric ? baseType.GetGenericTypeDefinition() : null;
Type[] baseTypeArgs = baseIsConstructedGeneric ? baseType.GetGenericArguments() : null;

// Single pass over all types
foreach (Type type in EnumerateAllTypesSafely())
{
if (!intrinsicTypePolicy.IsAllowed(type))
// Existing: check closed/non-generic candidates
if (intrinsicTypePolicy.IsAllowed(type) && typeCompatibilityPolicy.IsCompatible(baseType, type))
{
result.Add(type);
continue;
}
if (!typeCompatibilityPolicy.IsCompatible(baseType, type))

// New: try to close open generic candidates
if (baseIsConstructedGeneric && type.IsGenericTypeDefinition && Attribute.IsDefined(type, typeof(SerializableAttribute)))
{
continue;
Type closedType = TryCloseGenericType(type, baseGenericDef, baseTypeArgs);
if (closedType != null && intrinsicTypePolicy.IsAllowed(closedType))
{
result.Add(closedType);
}
}

result.Add(type);
}

// Include the base type itself if allowed
if (intrinsicTypePolicy.IsAllowed(baseType) && typeCompatibilityPolicy.IsCompatible(baseType, baseType))
{
result.Add(baseType);
Expand Down Expand Up @@ -99,6 +109,104 @@ private static IEnumerable<Type> EnumerateAllTypesSafely ()
}
}
}
private static Type TryCloseGenericType (Type openCandidateType, Type baseGenericDef, Type[] baseTypeArgs)
{
// openCandidateType is e.g. ConstantValueProvider<T>
// baseGenericDef is e.g. IValueProvider<>
// baseTypeArgs is e.g. [int]

Type[] candidateGenericParams = openCandidateType.GetGenericArguments();
Type[] resolvedArgs = new Type[candidateGenericParams.Length];

// Walk the candidate's interfaces to find one matching the base generic definition
foreach (Type iface in openCandidateType.GetInterfaces())
{
if (!iface.IsGenericType) continue;
if (iface.GetGenericTypeDefinition() != baseGenericDef) continue;

Type[] ifaceArgs = iface.GetGenericArguments();
if (ifaceArgs.Length != baseTypeArgs.Length) continue;

if (TryMapTypeArguments(candidateGenericParams, ifaceArgs, baseTypeArgs, resolvedArgs))
{
return TryMakeGenericTypeSafe(openCandidateType, resolvedArgs);
}
}

// Walk base class chain
for (Type t = openCandidateType.BaseType; t != null && t != typeof(object); t = t.BaseType)
{
if (!t.IsGenericType) continue;
if (t.GetGenericTypeDefinition() != baseGenericDef) continue;

Type[] tArgs = t.GetGenericArguments();
if (tArgs.Length != baseTypeArgs.Length) continue;

if (TryMapTypeArguments(candidateGenericParams, tArgs, baseTypeArgs, resolvedArgs))
{
return TryMakeGenericTypeSafe(openCandidateType, resolvedArgs);
}
}

return null;
}

private static bool TryMapTypeArguments (Type[] candidateGenericParams, Type[] ifaceArgs, Type[] baseTypeArgs, Type[] resolvedArgs)
{
// Reset
for (int i = 0; i < resolvedArgs.Length; i++)
{
resolvedArgs[i] = null;
}

// For each type argument in the interface/base, map it back to the candidate's generic parameter
// e.g. IValueProvider<T> has ifaceArgs=[T], baseTypeArgs=[int]
// We need to find that T is candidateGenericParams[0], so resolvedArgs[0] = int
for (int i = 0; i < ifaceArgs.Length; i++)
{
Type ifaceArg = ifaceArgs[i];
Type targetArg = baseTypeArgs[i];

if (ifaceArg.IsGenericParameter)
{
int position = ifaceArg.GenericParameterPosition;
if (position < 0 || position >= resolvedArgs.Length) return false;

if (resolvedArgs[position] != null && resolvedArgs[position] != targetArg)
{
// Conflicting mapping for the same parameter
return false;
}
resolvedArgs[position] = targetArg;
}
else
{
// The interface argument is already concrete — it must match exactly
if (ifaceArg != targetArg) return false;
}
}

// Check all parameters were resolved
for (int i = 0; i < resolvedArgs.Length; i++)
{
if (resolvedArgs[i] == null) return false;
}

return true;
}

private static Type TryMakeGenericTypeSafe (Type openType, Type[] typeArgs)
{
try
{
return openType.MakeGenericType(typeArgs);
}
catch (ArgumentException)
{
// Constraint violation (e.g. where T : struct but we passed a class)
return null;
}
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,11 @@ public sealed class TypeCandiateService
{

private readonly ITypeCandiateProvider typeCandiateProvider;
private readonly IIntrinsicTypePolicy intrinsicTypePolicy;
private readonly ITypeCompatibilityPolicy typeCompatibilityPolicy;

private readonly Dictionary<Type, Type[]> typeCache = new Dictionary<Type, Type[]>();

public TypeCandiateService (ITypeCandiateProvider typeCandiateProvider, IIntrinsicTypePolicy intrinsicTypePolicy, ITypeCompatibilityPolicy typeCompatibilityPolicy)
public TypeCandiateService (ITypeCandiateProvider typeCandiateProvider)
{
this.typeCandiateProvider = typeCandiateProvider ?? throw new ArgumentNullException(nameof(typeCandiateProvider));
this.intrinsicTypePolicy = intrinsicTypePolicy ?? throw new ArgumentNullException(nameof(intrinsicTypePolicy));
this.typeCompatibilityPolicy = typeCompatibilityPolicy ?? throw new ArgumentNullException(nameof(typeCompatibilityPolicy));
}

public IReadOnlyList<Type> GetDisplayableTypes (Type baseType)
Expand All @@ -30,11 +25,8 @@ public IReadOnlyList<Type> GetDisplayableTypes (Type baseType)
{
return cachedTypes;
}

var candiateTypes = typeCandiateProvider.GetTypeCandidates(baseType);
var result = candiateTypes
.Where(intrinsicTypePolicy.IsAllowed)
.Where(t => typeCompatibilityPolicy.IsCompatible(baseType, t))

var result = typeCandiateProvider.GetTypeCandidates(baseType)
.Distinct()
.ToArray();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ static TypeSearchService ()
TypeCandiateProvider = DefaultTypeCandiateProvider.Instance;
#endif

TypeCandiateService = new TypeCandiateService(TypeCandiateProvider, IntrinsicTypePolicy, TypeCompatibilityPolicy);
TypeCandiateService = new TypeCandiateService(TypeCandiateProvider);
}
}
}
Loading