A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Reflection;
5using System.Runtime.CompilerServices;
6using UnityObject = UnityEngine.Object;
7
8namespace Unity.VisualScripting
9{
10 public static class MemberUtility
11 {
12 static MemberUtility()
13 {
14 ExtensionMethodsCache = new Lazy<ExtensionMethodCache>(() => new ExtensionMethodCache(), true);
15 InheritedExtensionMethodsCache = new Lazy<Dictionary<Type, MethodInfo[]>>(() => new Dictionary<Type, MethodInfo[]>(), true);
16 GenericExtensionMethods = new Lazy<HashSet<MethodInfo>>(() => new HashSet<MethodInfo>(), true);
17 }
18
19 // The process of resolving generic methods is very expensive.
20 // Cache the results for each this parameter type.
21 private static readonly Lazy<ExtensionMethodCache> ExtensionMethodsCache;
22 private static readonly Lazy<Dictionary<Type, MethodInfo[]>> InheritedExtensionMethodsCache;
23 private static readonly Lazy<HashSet<MethodInfo>> GenericExtensionMethods;
24
25 public static bool IsOperator(this MethodInfo method)
26 {
27 return method.IsSpecialName && OperatorUtility.operatorNames.ContainsKey(method.Name);
28 }
29
30 public static bool IsUserDefinedConversion(this MethodInfo method)
31 {
32 return method.IsSpecialName && (method.Name == "op_Implicit" || method.Name == "op_Explicit");
33 }
34
35 /// <remarks>This may return an open-constructed method as well.</remarks>
36 public static MethodInfo MakeGenericMethodVia(this MethodInfo openConstructedMethod, params Type[] closedConstructedParameterTypes)
37 {
38 Ensure.That(nameof(openConstructedMethod)).IsNotNull(openConstructedMethod);
39 Ensure.That(nameof(closedConstructedParameterTypes)).IsNotNull(closedConstructedParameterTypes);
40
41 if (!openConstructedMethod.ContainsGenericParameters)
42 {
43 // The method contains no generic parameters,
44 // it is by definition already resolved.
45 return openConstructedMethod;
46 }
47
48 var openConstructedParameterTypes = openConstructedMethod.GetParameters().Select(p => p.ParameterType).ToArray();
49
50 if (openConstructedParameterTypes.Length != closedConstructedParameterTypes.Length)
51 {
52 throw new ArgumentOutOfRangeException(nameof(closedConstructedParameterTypes));
53 }
54
55 var resolvedGenericParameters = new Dictionary<Type, Type>();
56
57 for (var i = 0; i < openConstructedParameterTypes.Length; i++)
58 {
59 // Resolve each open-constructed parameter type via the equivalent
60 // closed-constructed parameter type.
61
62 var openConstructedParameterType = openConstructedParameterTypes[i];
63 var closedConstructedParameterType = closedConstructedParameterTypes[i];
64
65 openConstructedParameterType.MakeGenericTypeVia(closedConstructedParameterType, resolvedGenericParameters);
66 }
67
68 // Construct the final closed-constructed method from the resolved arguments
69
70 var openConstructedGenericArguments = openConstructedMethod.GetGenericArguments();
71 var closedConstructedGenericArguments = openConstructedGenericArguments.Select(openConstructedGenericArgument =>
72 {
73 // If the generic argument has been successfully resolved, use it;
74 // otherwise, leave the open-constructed argument in place.
75
76 if (resolvedGenericParameters.ContainsKey(openConstructedGenericArgument))
77 {
78 return resolvedGenericParameters[openConstructedGenericArgument];
79 }
80 else
81 {
82 return openConstructedGenericArgument;
83 }
84 }).ToArray();
85
86 return openConstructedMethod.MakeGenericMethod(closedConstructedGenericArguments);
87 }
88
89 public static bool IsGenericExtension(this MethodInfo methodInfo)
90 {
91 return GenericExtensionMethods.Value.Contains(methodInfo);
92 }
93
94 private static IEnumerable<MethodInfo> GetInheritedExtensionMethods(Type thisArgumentType)
95 {
96 var methodInfos = ExtensionMethodsCache.Value.Cache;
97 foreach (var extensionMethod in methodInfos)
98 {
99 var compatibleThis = extensionMethod.GetParameters()[0].ParameterType.CanMakeGenericTypeVia(thisArgumentType);
100
101 if (compatibleThis)
102 {
103 if (extensionMethod.ContainsGenericParameters)
104 {
105 var closedConstructedParameterTypes = thisArgumentType.Yield().Concat(extensionMethod.GetParametersWithoutThis().Select(p => p.ParameterType));
106
107 var closedConstructedMethod = extensionMethod.MakeGenericMethodVia(closedConstructedParameterTypes.ToArray());
108
109 GenericExtensionMethods.Value.Add(closedConstructedMethod);
110
111 yield return closedConstructedMethod;
112 }
113 else
114 {
115 yield return extensionMethod;
116 }
117 }
118 }
119 }
120
121 public static IEnumerable<MethodInfo> GetExtensionMethods(this Type thisArgumentType, bool inherited = true)
122 {
123 if (inherited)
124 {
125 lock (InheritedExtensionMethodsCache)
126 {
127 if (!InheritedExtensionMethodsCache.Value.TryGetValue(thisArgumentType, out var inheritedExtensionMethods))
128 {
129 inheritedExtensionMethods = GetInheritedExtensionMethods(thisArgumentType).ToArray();
130 InheritedExtensionMethodsCache.Value.Add(thisArgumentType, inheritedExtensionMethods);
131 }
132
133 return inheritedExtensionMethods;
134 }
135 }
136 else
137 {
138 var methodInfos = ExtensionMethodsCache.Value.Cache;
139 return methodInfos.Where(method => method.GetParameters()[0].ParameterType == thisArgumentType);
140 }
141 }
142
143 public static bool IsExtension(this MethodInfo methodInfo)
144 {
145 return methodInfo.HasAttribute<ExtensionAttribute>(false);
146 }
147
148 public static bool IsExtensionMethod(this MemberInfo memberInfo)
149 {
150 return memberInfo is MethodInfo methodInfo && methodInfo.IsExtension();
151 }
152
153 public static Delegate CreateDelegate(this MethodInfo methodInfo, Type delegateType)
154 {
155 return Delegate.CreateDelegate(delegateType, methodInfo);
156 }
157
158 public static bool IsAccessor(this MemberInfo memberInfo)
159 {
160 return memberInfo is FieldInfo || memberInfo is PropertyInfo;
161 }
162
163 public static Type GetAccessorType(this MemberInfo memberInfo)
164 {
165 if (memberInfo is FieldInfo)
166 {
167 return ((FieldInfo)memberInfo).FieldType;
168 }
169 else if (memberInfo is PropertyInfo)
170 {
171 return ((PropertyInfo)memberInfo).PropertyType;
172 }
173 else
174 {
175 return null;
176 }
177 }
178
179 public static bool IsPubliclyGettable(this MemberInfo memberInfo)
180 {
181 if (memberInfo is FieldInfo)
182 {
183 return ((FieldInfo)memberInfo).IsPublic;
184 }
185 else if (memberInfo is PropertyInfo)
186 {
187 var propertyInfo = (PropertyInfo)memberInfo;
188
189 return propertyInfo.CanRead && propertyInfo.GetGetMethod(false) != null;
190 }
191 else if (memberInfo is MethodInfo)
192 {
193 return ((MethodInfo)memberInfo).IsPublic;
194 }
195 else if (memberInfo is ConstructorInfo)
196 {
197 return ((ConstructorInfo)memberInfo).IsPublic;
198 }
199 else
200 {
201 throw new NotSupportedException();
202 }
203 }
204
205 private static Type ExtendedDeclaringType(this MemberInfo memberInfo)
206 {
207 if (memberInfo is MethodInfo methodInfo && methodInfo.IsExtension())
208 {
209 return methodInfo.GetParameters()[0].ParameterType;
210 }
211 else
212 {
213 return memberInfo.DeclaringType;
214 }
215 }
216
217 public static Type ExtendedDeclaringType(this MemberInfo memberInfo, bool invokeAsExtension)
218 {
219 if (invokeAsExtension)
220 {
221 return memberInfo.ExtendedDeclaringType();
222 }
223 else
224 {
225 return memberInfo.DeclaringType;
226 }
227 }
228
229 public static bool IsStatic(this PropertyInfo propertyInfo)
230 {
231 return (propertyInfo.GetGetMethod(true)?.IsStatic ?? false) ||
232 (propertyInfo.GetSetMethod(true)?.IsStatic ?? false);
233 }
234
235 public static bool IsStatic(this MemberInfo memberInfo)
236 {
237 if (memberInfo is FieldInfo)
238 {
239 return ((FieldInfo)memberInfo).IsStatic;
240 }
241 else if (memberInfo is PropertyInfo)
242 {
243 return ((PropertyInfo)memberInfo).IsStatic();
244 }
245 else if (memberInfo is MethodBase)
246 {
247 return ((MethodBase)memberInfo).IsStatic;
248 }
249 else
250 {
251 throw new NotSupportedException();
252 }
253 }
254
255 private static IEnumerable<ParameterInfo> GetParametersWithoutThis(this MethodBase methodBase)
256 {
257 return methodBase.GetParameters().Skip(methodBase.IsExtensionMethod() ? 1 : 0);
258 }
259
260 public static bool IsInvokedAsExtension(this MethodBase methodBase, Type targetType)
261 {
262 return methodBase.IsExtensionMethod() && methodBase.DeclaringType != targetType;
263 }
264
265 public static IEnumerable<ParameterInfo> GetInvocationParameters(this MethodBase methodBase, bool invokeAsExtension)
266 {
267 if (invokeAsExtension)
268 {
269 return methodBase.GetParametersWithoutThis();
270 }
271 else
272 {
273 return methodBase.GetParameters();
274 }
275 }
276
277 public static IEnumerable<ParameterInfo> GetInvocationParameters(this MethodBase methodBase, Type targetType)
278 {
279 return methodBase.GetInvocationParameters(methodBase.IsInvokedAsExtension(targetType));
280 }
281
282 public static Type UnderlyingParameterType(this ParameterInfo parameterInfo)
283 {
284 if (parameterInfo.ParameterType.IsByRef)
285 {
286 return parameterInfo.ParameterType.GetElementType();
287 }
288 else
289 {
290 return parameterInfo.ParameterType;
291 }
292 }
293
294 // https://stackoverflow.com/questions/9977530/
295 // https://stackoverflow.com/questions/16186694
296 public static bool HasDefaultValue(this ParameterInfo parameterInfo)
297 {
298 return (parameterInfo.Attributes & ParameterAttributes.HasDefault) == ParameterAttributes.HasDefault;
299 }
300
301 public static object DefaultValue(this ParameterInfo parameterInfo)
302 {
303 if (parameterInfo.HasDefaultValue())
304 {
305 var defaultValue = parameterInfo.DefaultValue;
306
307 // https://stackoverflow.com/questions/45393580
308 if (defaultValue == null && parameterInfo.ParameterType.IsValueType)
309 {
310 defaultValue = parameterInfo.ParameterType.Default();
311 }
312
313 return defaultValue;
314 }
315 else
316 {
317 return parameterInfo.UnderlyingParameterType().Default();
318 }
319 }
320
321 public static object PseudoDefaultValue(this ParameterInfo parameterInfo)
322 {
323 if (parameterInfo.HasDefaultValue())
324 {
325 var defaultValue = parameterInfo.DefaultValue;
326
327 // https://stackoverflow.com/questions/45393580
328 if (defaultValue == null && parameterInfo.ParameterType.IsValueType)
329 {
330 defaultValue = parameterInfo.ParameterType.PseudoDefault();
331 }
332
333 return defaultValue;
334 }
335 else
336 {
337 return parameterInfo.UnderlyingParameterType().PseudoDefault();
338 }
339 }
340
341 public static bool AllowsNull(this ParameterInfo parameterInfo)
342 {
343 var type = parameterInfo.ParameterType;
344
345 return (type.IsReferenceType() && parameterInfo.HasAttribute<AllowsNullAttribute>()) || Nullable.GetUnderlyingType(type) != null;
346 }
347
348 // https://stackoverflow.com/questions/30102174/
349 public static bool HasOutModifier(this ParameterInfo parameterInfo)
350 {
351 Ensure.That(nameof(parameterInfo)).IsNotNull(parameterInfo);
352
353 // Checking for IsOut is not enough, because parameters marked with the [Out] attribute
354 // also return true, while not necessarily having the "out" modifier. This is common for P/Invoke,
355 // for example in Unity's ParticleSystem.GetParticles.
356 return parameterInfo.IsOut && parameterInfo.ParameterType.IsByRef;
357 }
358
359 public static bool CanWrite(this FieldInfo fieldInfo)
360 {
361 return !(fieldInfo.IsInitOnly || fieldInfo.IsLiteral);
362 }
363
364 public static Member ToManipulator(this MemberInfo memberInfo)
365 {
366 return ToManipulator(memberInfo, memberInfo.DeclaringType);
367 }
368
369 public static Member ToManipulator(this MemberInfo memberInfo, Type targetType)
370 {
371 if (memberInfo is FieldInfo fieldInfo)
372 {
373 return fieldInfo.ToManipulator(targetType);
374 }
375
376 if (memberInfo is PropertyInfo propertyInfo)
377 {
378 return propertyInfo.ToManipulator(targetType);
379 }
380
381 if (memberInfo is MethodInfo methodInfo)
382 {
383 return methodInfo.ToManipulator(targetType);
384 }
385
386 if (memberInfo is ConstructorInfo constructorInfo)
387 {
388 return constructorInfo.ToManipulator(targetType);
389 }
390
391 throw new InvalidOperationException();
392 }
393
394 public static Member ToManipulator(this FieldInfo fieldInfo, Type targetType)
395 {
396 return new Member(targetType, fieldInfo);
397 }
398
399 public static Member ToManipulator(this PropertyInfo propertyInfo, Type targetType)
400 {
401 return new Member(targetType, propertyInfo);
402 }
403
404 public static Member ToManipulator(this MethodInfo methodInfo, Type targetType)
405 {
406 return new Member(targetType, methodInfo);
407 }
408
409 public static Member ToManipulator(this ConstructorInfo constructorInfo, Type targetType)
410 {
411 return new Member(targetType, constructorInfo);
412 }
413
414 public static ConstructorInfo GetConstructorAccepting(this Type type, Type[] paramTypes, bool nonPublic)
415 {
416 var bindingFlags = BindingFlags.Instance | BindingFlags.Public;
417
418 if (nonPublic)
419 {
420 bindingFlags |= BindingFlags.NonPublic;
421 }
422
423 return type
424 .GetConstructors(bindingFlags)
425 .FirstOrDefault(constructor =>
426 {
427 var parameters = constructor.GetParameters();
428
429 if (parameters.Length != paramTypes.Length)
430 {
431 return false;
432 }
433
434 for (var i = 0; i < parameters.Length; i++)
435 {
436 if (paramTypes[i] == null)
437 {
438 if (!parameters[i].ParameterType.IsNullable())
439 {
440 return false;
441 }
442 }
443 else
444 {
445 if (!parameters[i].ParameterType.IsAssignableFrom(paramTypes[i]))
446 {
447 return false;
448 }
449 }
450 }
451
452 return true;
453 });
454 }
455
456 public static ConstructorInfo GetConstructorAccepting(this Type type, params Type[] paramTypes)
457 {
458 return GetConstructorAccepting(type, paramTypes, true);
459 }
460
461 public static ConstructorInfo GetPublicConstructorAccepting(this Type type, params Type[] paramTypes)
462 {
463 return GetConstructorAccepting(type, paramTypes, false);
464 }
465
466 public static ConstructorInfo GetDefaultConstructor(this Type type)
467 {
468 return GetConstructorAccepting(type);
469 }
470
471 public static ConstructorInfo GetPublicDefaultConstructor(this Type type)
472 {
473 return GetPublicConstructorAccepting(type);
474 }
475
476 public static MemberInfo[] GetExtendedMember(this Type type, string name, MemberTypes types, BindingFlags flags)
477 {
478 var members = type.GetMember(name, types, flags).ToList();
479
480 if (types.HasFlag(MemberTypes.Method)) // Check for extension methods
481 {
482 members.AddRange(type.GetExtensionMethods()
483 .Where(extension => extension.Name == name)
484 .Cast<MemberInfo>());
485 }
486
487 return members.ToArray();
488 }
489
490 public static MemberInfo[] GetExtendedMembers(this Type type, BindingFlags flags)
491 {
492 var members = type.GetMembers(flags).ToHashSet();
493
494 foreach (var extensionMethod in type.GetExtensionMethods())
495 {
496 members.Add(extensionMethod);
497 }
498
499 return members.ToArray();
500 }
501
502 #region Signature Disambiguation
503
504 private static bool NameMatches(this MemberInfo member, string name)
505 {
506 return member.Name == name;
507 }
508
509 private static bool ParametersMatch(this MethodBase methodBase, IEnumerable<Type> parameterTypes, bool invokeAsExtension)
510 {
511 Ensure.That(nameof(parameterTypes)).IsNotNull(parameterTypes);
512
513 return methodBase.GetInvocationParameters(invokeAsExtension).Select(paramInfo => paramInfo.ParameterType).SequenceEqual(parameterTypes);
514 }
515
516 private static bool GenericArgumentsMatch(this MethodInfo method, IEnumerable<Type> genericArgumentTypes)
517 {
518 Ensure.That(nameof(genericArgumentTypes)).IsNotNull(genericArgumentTypes);
519
520 if (method.ContainsGenericParameters)
521 {
522 return false;
523 }
524
525 return method.GetGenericArguments().SequenceEqual(genericArgumentTypes);
526 }
527
528 public static bool SignatureMatches(this FieldInfo field, string name)
529 {
530 return field.NameMatches(name);
531 }
532
533 public static bool SignatureMatches(this PropertyInfo property, string name)
534 {
535 return property.NameMatches(name);
536 }
537
538 public static bool SignatureMatches(this ConstructorInfo constructor, string name, IEnumerable<Type> parameterTypes)
539 {
540 return constructor.NameMatches(name) && constructor.ParametersMatch(parameterTypes, false);
541 }
542
543 public static bool SignatureMatches(this MethodInfo method, string name, IEnumerable<Type> parameterTypes, bool invokeAsExtension)
544 {
545 return method.NameMatches(name) && method.ParametersMatch(parameterTypes, invokeAsExtension) && !method.ContainsGenericParameters;
546 }
547
548 public static bool SignatureMatches(this MethodInfo method, string name, IEnumerable<Type> parameterTypes, IEnumerable<Type> genericArgumentTypes, bool invokeAsExtension)
549 {
550 return method.NameMatches(name) && method.ParametersMatch(parameterTypes, invokeAsExtension) && method.GenericArgumentsMatch(genericArgumentTypes);
551 }
552
553 public static FieldInfo GetFieldUnambiguous(this Type type, string name, BindingFlags flags)
554 {
555 Ensure.That(nameof(type)).IsNotNull(type);
556 Ensure.That(nameof(name)).IsNotNull(name);
557
558 flags |= BindingFlags.DeclaredOnly;
559
560 while (type != null)
561 {
562 var field = type.GetField(name, flags);
563
564 if (field != null)
565 {
566 return field;
567 }
568
569 type = type.BaseType;
570 }
571
572 return null;
573 }
574
575 public static PropertyInfo GetPropertyUnambiguous(this Type type, string name, BindingFlags flags)
576 {
577 Ensure.That(nameof(type)).IsNotNull(type);
578 Ensure.That(nameof(name)).IsNotNull(name);
579
580 flags |= BindingFlags.DeclaredOnly;
581
582 while (type != null)
583 {
584 var property = type.GetProperty(name, flags);
585
586 if (property != null)
587 {
588 return property;
589 }
590
591 type = type.BaseType;
592 }
593
594 return null;
595 }
596
597 public static MethodInfo GetMethodUnambiguous(this Type type, string name, BindingFlags flags)
598 {
599 Ensure.That(nameof(type)).IsNotNull(type);
600 Ensure.That(nameof(name)).IsNotNull(name);
601
602 flags |= BindingFlags.DeclaredOnly;
603
604 while (type != null)
605 {
606 var method = type.GetMethod(name, flags);
607
608 if (method != null)
609 {
610 return method;
611 }
612
613 type = type.BaseType;
614 }
615
616 return null;
617 }
618
619 private static TMemberInfo DisambiguateHierarchy<TMemberInfo>(this IEnumerable<TMemberInfo> members, Type type) where TMemberInfo : MemberInfo
620 {
621 while (type != null)
622 {
623 foreach (var member in members)
624 {
625 var methodInfo = member as MethodInfo;
626 var invokedAsExtension = methodInfo != null && methodInfo.IsInvokedAsExtension(type);
627
628 if (member.ExtendedDeclaringType(invokedAsExtension) == type)
629 {
630 return member;
631 }
632 }
633
634 type = type.BaseType;
635 }
636
637 return null;
638 }
639
640 public static FieldInfo Disambiguate(this IEnumerable<FieldInfo> fields, Type type)
641 {
642 Ensure.That(nameof(fields)).IsNotNull(fields);
643 Ensure.That(nameof(type)).IsNotNull(type);
644
645 return fields.DisambiguateHierarchy(type);
646 }
647
648 public static PropertyInfo Disambiguate(this IEnumerable<PropertyInfo> properties, Type type)
649 {
650 Ensure.That(nameof(properties)).IsNotNull(properties);
651 Ensure.That(nameof(type)).IsNotNull(type);
652
653 return properties.DisambiguateHierarchy(type);
654 }
655
656 public static ConstructorInfo Disambiguate(this IEnumerable<ConstructorInfo> constructors, Type type, IEnumerable<Type> parameterTypes)
657 {
658 Ensure.That(nameof(constructors)).IsNotNull(constructors);
659 Ensure.That(nameof(type)).IsNotNull(type);
660 Ensure.That(nameof(parameterTypes)).IsNotNull(parameterTypes);
661
662 return constructors.Where(m => m.ParametersMatch(parameterTypes, false) && !m.ContainsGenericParameters).DisambiguateHierarchy(type);
663 }
664
665 public static MethodInfo Disambiguate(this IEnumerable<MethodInfo> methods, Type type, IEnumerable<Type> parameterTypes)
666 {
667 Ensure.That(nameof(methods)).IsNotNull(methods);
668 Ensure.That(nameof(type)).IsNotNull(type);
669 Ensure.That(nameof(parameterTypes)).IsNotNull(parameterTypes);
670
671 return methods.Where(m => m.ParametersMatch(parameterTypes, m.IsInvokedAsExtension(type)) && !m.ContainsGenericParameters).DisambiguateHierarchy(type);
672 }
673
674 public static MethodInfo Disambiguate(this IEnumerable<MethodInfo> methods, Type type, IEnumerable<Type> parameterTypes, IEnumerable<Type> genericArgumentTypes)
675 {
676 Ensure.That(nameof(methods)).IsNotNull(methods);
677 Ensure.That(nameof(type)).IsNotNull(type);
678 Ensure.That(nameof(parameterTypes)).IsNotNull(parameterTypes);
679 Ensure.That(nameof(genericArgumentTypes)).IsNotNull(genericArgumentTypes);
680
681 return methods.Where(m => m.ParametersMatch(parameterTypes, m.IsInvokedAsExtension(type)) && m.GenericArgumentsMatch(genericArgumentTypes)).DisambiguateHierarchy(type);
682 }
683
684 #endregion
685 }
686
687 internal class ExtensionMethodCache
688 {
689 internal ExtensionMethodCache()
690 {
691 // Cache a list of all extension methods in assemblies
692 // http://stackoverflow.com/a/299526
693 Cache = RuntimeCodebase.types
694 .Where(type => type.IsStatic() && !type.IsGenericType && !type.IsNested)
695 .SelectMany(type => type.GetMethods())
696 .Where(method => method.IsExtension())
697 .ToArray();
698 }
699
700 internal readonly MethodInfo[] Cache;
701 }
702}