A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Linq;
5using System.Reflection;
6using UnityEngine;
7
8namespace Unity.VisualScripting
9{
10 public static class ConversionUtility
11 {
12 public enum ConversionType
13 {
14 Impossible,
15 Identity,
16 Upcast,
17 Downcast,
18 NumericImplicit,
19 NumericExplicit,
20 UserDefinedImplicit,
21 UserDefinedExplicit,
22 UserDefinedThenNumericImplicit,
23 UserDefinedThenNumericExplicit,
24 UnityHierarchy,
25 EnumerableToArray,
26 EnumerableToList,
27 ToString
28 }
29
30 private const BindingFlags UserDefinedBindingFlags = BindingFlags.Static | BindingFlags.Public;
31
32 private static readonly Dictionary<ConversionQuery, ConversionType> conversionTypesCache = new Dictionary<ConversionQuery, ConversionType>(new ConversionQueryComparer());
33 private static readonly Dictionary<ConversionQuery, MethodInfo[]> userConversionMethodsCache = new Dictionary<ConversionQuery, MethodInfo[]>(new ConversionQueryComparer());
34
35 private static bool RespectsIdentity(Type source, Type destination)
36 {
37 return source == destination;
38 }
39
40 private static bool IsUpcast(Type source, Type destination)
41 {
42 return destination.IsAssignableFrom(source);
43 }
44
45 private static bool IsDowncast(Type source, Type destination)
46 {
47 return source.IsAssignableFrom(destination);
48 }
49
50 private static bool ExpectsString(Type source, Type destination)
51 {
52 return destination == typeof(string);
53 }
54
55 public static bool HasImplicitNumericConversion(Type source, Type destination)
56 {
57 return implicitNumericConversions.ContainsKey(source) && implicitNumericConversions[source].Contains(destination);
58 }
59
60 public static bool HasExplicitNumericConversion(Type source, Type destination)
61 {
62 return explicitNumericConversions.ContainsKey(source) && explicitNumericConversions[source].Contains(destination);
63 }
64
65 public static bool HasNumericConversion(Type source, Type destination)
66 {
67 return HasImplicitNumericConversion(source, destination) || HasExplicitNumericConversion(source, destination);
68 }
69
70 private static IEnumerable<MethodInfo> FindUserDefinedConversionMethods(ConversionQuery query)
71 {
72 var source = query.source;
73 var destination = query.destination;
74
75 var sourceMethods = source.GetMethods(UserDefinedBindingFlags)
76 .Where(m => m.IsUserDefinedConversion());
77
78 var destinationMethods = destination.GetMethods(UserDefinedBindingFlags)
79 .Where(m => m.IsUserDefinedConversion());
80
81 return sourceMethods.Concat(destinationMethods).Where
82 (
83 m => m.GetParameters()[0].ParameterType.IsAssignableFrom(source) ||
84 source.IsAssignableFrom(m.GetParameters()[0].ParameterType)
85 );
86 }
87
88 // Returning an array directly so that the enumeration in
89 // UserDefinedConversion does not allocate memory
90 private static MethodInfo[] GetUserDefinedConversionMethods(Type source, Type destination)
91 {
92 var query = new ConversionQuery(source, destination);
93
94 if (!userConversionMethodsCache.ContainsKey(query))
95 {
96 userConversionMethodsCache.Add(query, FindUserDefinedConversionMethods(query).ToArray());
97 }
98
99 return userConversionMethodsCache[query];
100 }
101
102 private static ConversionType GetUserDefinedConversionType(Type source, Type destination)
103 {
104 var conversionMethods = GetUserDefinedConversionMethods(source, destination);
105
106 // Duplicate user defined conversions are not allowed, so FirstOrDefault is safe.
107
108 // Look for direct conversions.
109 var conversionMethod = conversionMethods.FirstOrDefault(m => m.ReturnType == destination);
110
111 if (conversionMethod != null)
112 {
113 if (conversionMethod.Name == "op_Implicit")
114 {
115 return ConversionType.UserDefinedImplicit;
116 }
117 else if (conversionMethod.Name == "op_Explicit")
118 {
119 return ConversionType.UserDefinedExplicit;
120 }
121 }
122 // Primitive types can skip the middleman cast, even if it is explicit.
123 else if (destination.IsPrimitive && destination != typeof(IntPtr) && destination != typeof(UIntPtr))
124 {
125 // Look for implicit conversions.
126 conversionMethod = conversionMethods.FirstOrDefault(m => HasImplicitNumericConversion(m.ReturnType, destination));
127
128 if (conversionMethod != null)
129 {
130 if (conversionMethod.Name == "op_Implicit")
131 {
132 return ConversionType.UserDefinedThenNumericImplicit;
133 }
134 else if (conversionMethod.Name == "op_Explicit")
135 {
136 return ConversionType.UserDefinedThenNumericExplicit;
137 }
138 }
139 // Look for explicit conversions.
140 else
141 {
142 conversionMethod = conversionMethods.FirstOrDefault(m => HasExplicitNumericConversion(m.ReturnType, destination));
143
144 if (conversionMethod != null)
145 {
146 return ConversionType.UserDefinedThenNumericExplicit;
147 }
148 }
149 }
150
151 return ConversionType.Impossible;
152 }
153
154 private static bool HasEnumerableToArrayConversion(Type source, Type destination)
155 {
156 return source != typeof(string) &&
157 typeof(IEnumerable).IsAssignableFrom(source) &&
158 destination.IsArray &&
159 destination.GetArrayRank() == 1;
160 }
161
162 private static bool HasEnumerableToListConversion(Type source, Type destination)
163 {
164 return source != typeof(string) &&
165 typeof(IEnumerable).IsAssignableFrom(source) &&
166 destination.IsGenericType &&
167 destination.GetGenericTypeDefinition() == typeof(List<>);
168 }
169
170 private static bool HasUnityHierarchyConversion(Type source, Type destination)
171 {
172 if (destination == typeof(GameObject))
173 {
174 return typeof(Component).IsAssignableFrom(source);
175 }
176 else if (typeof(Component).IsAssignableFrom(destination) || destination.IsInterface)
177 {
178 return source == typeof(GameObject) || typeof(Component).IsAssignableFrom(source);
179 }
180
181 return false;
182 }
183
184 private static bool IsValidConversion(ConversionType conversionType, bool guaranteed)
185 {
186 if (conversionType == ConversionType.Impossible)
187 {
188 return false;
189 }
190
191 if (guaranteed)
192 {
193 // Downcasts are not guaranteed to succeed.
194 if (conversionType == ConversionType.Downcast)
195 {
196 return false;
197 }
198 }
199
200 return true;
201 }
202
203 public static bool CanConvert(object value, Type type, bool guaranteed)
204 {
205 return IsValidConversion(GetRequiredConversion(value, type), guaranteed);
206 }
207
208 public static bool CanConvert(Type source, Type destination, bool guaranteed)
209 {
210 return IsValidConversion(GetRequiredConversion(source, destination), guaranteed);
211 }
212
213 public static object Convert(object value, Type type)
214 {
215 return Convert(value, type, GetRequiredConversion(value, type));
216 }
217
218 public static T Convert<T>(object value)
219 {
220 return (T)Convert(value, typeof(T));
221 }
222
223 public static bool TryConvert(object value, Type type, out object result, bool guaranteed)
224 {
225 var conversionType = GetRequiredConversion(value, type);
226
227 if (IsValidConversion(conversionType, guaranteed))
228 {
229 result = Convert(value, type, conversionType);
230 return true;
231 }
232
233 result = value;
234 return false;
235 }
236
237 public static bool TryConvert<T>(object value, out T result, bool guaranteed)
238 {
239 if (TryConvert(value, typeof(T), out var res, guaranteed))
240 {
241 result = (T)res;
242 return true;
243 }
244
245 result = default;
246 return false;
247 }
248
249 public static bool IsConvertibleTo(this Type source, Type destination, bool guaranteed)
250 {
251 return CanConvert(source, destination, guaranteed);
252 }
253
254 public static bool IsConvertibleTo(this object source, Type type, bool guaranteed)
255 {
256 return CanConvert(source, type, guaranteed);
257 }
258
259 public static bool IsConvertibleTo<T>(this object source, bool guaranteed)
260 {
261 return CanConvert(source, typeof(T), guaranteed);
262 }
263
264 public static object ConvertTo(this object source, Type type)
265 {
266 return Convert(source, type);
267 }
268
269 public static T ConvertTo<T>(this object source)
270 {
271 return (T)Convert(source, typeof(T));
272 }
273
274 public static ConversionType GetRequiredConversion(Type source, Type destination)
275 {
276 var query = new ConversionQuery(source, destination);
277
278 if (!conversionTypesCache.TryGetValue(query, out var conversionType))
279 {
280 conversionType = DetermineConversionType(query);
281 conversionTypesCache.Add(query, conversionType);
282 }
283
284 return conversionType;
285 }
286
287 private static ConversionType DetermineConversionType(ConversionQuery query)
288 {
289 var source = query.source;
290 var destination = query.destination;
291
292 if (source == null)
293 {
294 if (destination.IsNullable())
295 {
296 return ConversionType.Identity;
297 }
298 else
299 {
300 return ConversionType.Impossible;
301 }
302 }
303
304 Ensure.That(nameof(destination)).IsNotNull(destination);
305
306 if (RespectsIdentity(source, destination))
307 {
308 return ConversionType.Identity;
309 }
310 else if (IsUpcast(source, destination))
311 {
312 return ConversionType.Upcast;
313 }
314 else if (IsDowncast(source, destination))
315 {
316 return ConversionType.Downcast;
317 }
318 // Disabling *.ToString conversion, because it's more often than otherwise very confusing
319 /*else if (ExpectsString(source, destination))
320 {
321 return ConversionType.ToString;
322 }*/
323 else if (HasImplicitNumericConversion(source, destination))
324 {
325 return ConversionType.NumericImplicit;
326 }
327 else if (HasExplicitNumericConversion(source, destination))
328 {
329 return ConversionType.NumericExplicit;
330 }
331 else if (HasUnityHierarchyConversion(source, destination))
332 {
333 return ConversionType.UnityHierarchy;
334 }
335 else if (HasEnumerableToArrayConversion(source, destination))
336 {
337 return ConversionType.EnumerableToArray;
338 }
339 else if (HasEnumerableToListConversion(source, destination))
340 {
341 return ConversionType.EnumerableToList;
342 }
343 else
344 {
345 var userDefinedConversionType = GetUserDefinedConversionType(source, destination);
346
347 if (userDefinedConversionType != ConversionType.Impossible)
348 {
349 return userDefinedConversionType;
350 }
351 }
352
353 return ConversionType.Impossible;
354 }
355
356 public static ConversionType GetRequiredConversion(object value, Type type)
357 {
358 Ensure.That(nameof(type)).IsNotNull(type);
359
360 return GetRequiredConversion(value?.GetType(), type);
361 }
362
363 private static object NumericConversion(object value, Type type)
364 {
365 return System.Convert.ChangeType(value, type);
366 }
367
368 private static object UserDefinedConversion(ConversionType conversion, object value, Type type)
369 {
370 var valueType = value.GetType();
371 var conversionMethods = GetUserDefinedConversionMethods(valueType, type);
372
373 var numeric = conversion == ConversionType.UserDefinedThenNumericImplicit ||
374 conversion == ConversionType.UserDefinedThenNumericExplicit;
375
376 MethodInfo conversionMethod = null;
377
378 if (numeric)
379 {
380 foreach (var m in conversionMethods)
381 {
382 if (HasNumericConversion(m.ReturnType, type))
383 {
384 conversionMethod = m;
385 break;
386 }
387 }
388 }
389 else
390 {
391 foreach (var m in conversionMethods)
392 {
393 if (m.ReturnType == type)
394 {
395 conversionMethod = m;
396 break;
397 }
398 }
399 }
400
401 var result = conversionMethod.InvokeOptimized(null, value);
402
403 if (numeric)
404 {
405 result = NumericConversion(result, type);
406 }
407
408 return result;
409 }
410
411 private static object EnumerableToArrayConversion(object value, Type arrayType)
412 {
413 var elementType = arrayType.GetElementType();
414 var objectArray = ((IEnumerable)value).Cast<object>().Where(elementType.IsAssignableFrom).ToArray(); // Non-generic OfType
415 var typedArray = Array.CreateInstance(elementType, objectArray.Length);
416 objectArray.CopyTo(typedArray, 0);
417 return typedArray;
418 }
419
420 private static object EnumerableToListConversion(object value, Type listType)
421 {
422 var elementType = listType.GetGenericArguments()[0];
423 var objectArray = ((IEnumerable)value).Cast<object>().Where(elementType.IsAssignableFrom).ToArray(); // Non-generic OfType
424 var typedList = (IList)Activator.CreateInstance(listType);
425
426 for (var i = 0; i < objectArray.Length; i++)
427 {
428 typedList.Add(objectArray[i]);
429 }
430
431 return typedList;
432 }
433
434 private static object UnityHierarchyConversion(object value, Type type)
435 {
436 if (value.IsUnityNull())
437 {
438 return null;
439 }
440
441 if (type == typeof(GameObject) && value is Component)
442 {
443 return ((Component)value).gameObject;
444 }
445 else if (typeof(Component).IsAssignableFrom(type) || type.IsInterface)
446 {
447 if (value is Component)
448 {
449 return ((Component)value).GetComponent(type);
450 }
451 else if (value is GameObject)
452 {
453 return ((GameObject)value).GetComponent(type);
454 }
455 }
456
457 throw new InvalidConversionException();
458 }
459
460 private static object Convert(object value, Type type, ConversionType conversionType)
461 {
462 Ensure.That(nameof(type)).IsNotNull(type);
463
464 if (conversionType == ConversionType.Impossible)
465 {
466 throw new InvalidConversionException($"Cannot convert from '{value?.GetType().ToString() ?? "null"}' to '{type}'.");
467 }
468
469 try
470 {
471 switch (conversionType)
472 {
473 case ConversionType.Identity:
474 case ConversionType.Upcast:
475 case ConversionType.Downcast:
476 return value;
477
478 case ConversionType.ToString:
479 return value.ToString();
480
481 case ConversionType.NumericImplicit:
482 case ConversionType.NumericExplicit:
483 return NumericConversion(value, type);
484
485 case ConversionType.UserDefinedImplicit:
486 case ConversionType.UserDefinedExplicit:
487 case ConversionType.UserDefinedThenNumericImplicit:
488 case ConversionType.UserDefinedThenNumericExplicit:
489 return UserDefinedConversion(conversionType, value, type);
490
491 case ConversionType.EnumerableToArray:
492 return EnumerableToArrayConversion(value, type);
493
494 case ConversionType.EnumerableToList:
495 return EnumerableToListConversion(value, type);
496
497 case ConversionType.UnityHierarchy:
498 return UnityHierarchyConversion(value, type);
499
500 default:
501 throw new UnexpectedEnumValueException<ConversionType>(conversionType);
502 }
503 }
504 catch (Exception ex)
505 {
506 throw new InvalidConversionException($"Failed to convert from '{value?.GetType().ToString() ?? "null"}' to '{type}' via {conversionType}.", ex);
507 }
508 }
509
510 private struct ConversionQuery : IEquatable<ConversionQuery>
511 {
512 public readonly Type source;
513 public readonly Type destination;
514
515 public ConversionQuery(Type source, Type destination)
516 {
517 this.source = source;
518 this.destination = destination;
519 }
520
521 public bool Equals(ConversionQuery other)
522 {
523 return
524 source == other.source &&
525 destination == other.destination;
526 }
527
528 public override bool Equals(object obj)
529 {
530 if (!(obj is ConversionQuery))
531 {
532 return false;
533 }
534
535 return Equals((ConversionQuery)obj);
536 }
537
538 public override int GetHashCode()
539 {
540 return HashUtility.GetHashCode(source, destination);
541 }
542 }
543
544 // Make sure the equality comparer doesn't use boxing
545 private struct ConversionQueryComparer : IEqualityComparer<ConversionQuery>
546 {
547 public bool Equals(ConversionQuery x, ConversionQuery y)
548 {
549 return x.Equals(y);
550 }
551
552 public int GetHashCode(ConversionQuery obj)
553 {
554 return obj.GetHashCode();
555 }
556 }
557
558 #region Numeric Conversions
559
560 // https://msdn.microsoft.com/en-us/library/y5b434w4.aspx
561 private static readonly Dictionary<Type, HashSet<Type>> implicitNumericConversions = new Dictionary<Type, HashSet<Type>>()
562 {
563 {
564 typeof(sbyte),
565 new HashSet<Type>()
566 {
567 typeof(byte),
568 typeof(int),
569 typeof(long),
570 typeof(float),
571 typeof(double),
572 typeof(decimal)
573 }
574 },
575 {
576 typeof(byte),
577 new HashSet<Type>()
578 {
579 typeof(short),
580 typeof(ushort),
581 typeof(int),
582 typeof(uint),
583 typeof(long),
584 typeof(ulong),
585 typeof(float),
586 typeof(double),
587 typeof(decimal)
588 }
589 },
590 {
591 typeof(short),
592 new HashSet<Type>()
593 {
594 typeof(int),
595 typeof(long),
596 typeof(float),
597 typeof(double),
598 typeof(decimal)
599 }
600 },
601 {
602 typeof(ushort),
603 new HashSet<Type>()
604 {
605 typeof(int),
606 typeof(uint),
607 typeof(long),
608 typeof(ulong),
609 typeof(float),
610 typeof(double),
611 typeof(decimal),
612 }
613 },
614 {
615 typeof(int),
616 new HashSet<Type>()
617 {
618 typeof(long),
619 typeof(float),
620 typeof(double),
621 typeof(decimal)
622 }
623 },
624 {
625 typeof(uint),
626 new HashSet<Type>()
627 {
628 typeof(long),
629 typeof(ulong),
630 typeof(float),
631 typeof(double),
632 typeof(decimal)
633 }
634 },
635 {
636 typeof(long),
637 new HashSet<Type>()
638 {
639 typeof(float),
640 typeof(double),
641 typeof(decimal)
642 }
643 },
644 {
645 typeof(char),
646 new HashSet<Type>()
647 {
648 typeof(ushort),
649 typeof(int),
650 typeof(uint),
651 typeof(long),
652 typeof(ulong),
653 typeof(float),
654 typeof(double),
655 typeof(decimal)
656 }
657 },
658 {
659 typeof(float),
660 new HashSet<Type>()
661 {
662 typeof(double)
663 }
664 },
665 {
666 typeof(ulong),
667 new HashSet<Type>()
668 {
669 typeof(float),
670 typeof(double),
671 typeof(decimal)
672 }
673 },
674 };
675
676 // https://msdn.microsoft.com/en-us/library/yht2cx7b.aspx
677 private static readonly Dictionary<Type, HashSet<Type>> explicitNumericConversions = new Dictionary<Type, HashSet<Type>>()
678 {
679 {
680 typeof(sbyte),
681 new HashSet<Type>()
682 {
683 typeof(byte),
684 typeof(ushort),
685 typeof(uint),
686 typeof(ulong),
687 typeof(char)
688 }
689 },
690 {
691 typeof(byte),
692 new HashSet<Type>()
693 {
694 typeof(sbyte),
695 typeof(char)
696 }
697 },
698 {
699 typeof(short),
700 new HashSet<Type>()
701 {
702 typeof(sbyte),
703 typeof(byte),
704 typeof(ushort),
705 typeof(uint),
706 typeof(ulong),
707 typeof(char)
708 }
709 },
710 {
711 typeof(ushort),
712 new HashSet<Type>()
713 {
714 typeof(sbyte),
715 typeof(byte),
716 typeof(short),
717 typeof(char)
718 }
719 },
720 {
721 typeof(int),
722 new HashSet<Type>()
723 {
724 typeof(sbyte),
725 typeof(byte),
726 typeof(short),
727 typeof(ushort),
728 typeof(uint),
729 typeof(ulong),
730 typeof(char)
731 }
732 },
733 {
734 typeof(uint),
735 new HashSet<Type>()
736 {
737 typeof(sbyte),
738 typeof(byte),
739 typeof(short),
740 typeof(ushort),
741 typeof(int),
742 typeof(char)
743 }
744 },
745 {
746 typeof(long),
747 new HashSet<Type>()
748 {
749 typeof(sbyte),
750 typeof(byte),
751 typeof(short),
752 typeof(ushort),
753 typeof(int),
754 typeof(uint),
755 typeof(ulong),
756 typeof(char)
757 }
758 },
759 {
760 typeof(ulong),
761 new HashSet<Type>()
762 {
763 typeof(sbyte),
764 typeof(byte),
765 typeof(short),
766 typeof(ushort),
767 typeof(int),
768 typeof(uint),
769 typeof(long),
770 typeof(char)
771 }
772 },
773 {
774 typeof(char),
775 new HashSet<Type>()
776 {
777 typeof(sbyte),
778 typeof(byte),
779 typeof(short)
780 }
781 },
782 {
783 typeof(float),
784 new HashSet<Type>()
785 {
786 typeof(sbyte),
787 typeof(byte),
788 typeof(short),
789 typeof(ushort),
790 typeof(int),
791 typeof(uint),
792 typeof(long),
793 typeof(ulong),
794 typeof(char),
795 typeof(decimal)
796 }
797 },
798 {
799 typeof(double),
800 new HashSet<Type>()
801 {
802 typeof(sbyte),
803 typeof(byte),
804 typeof(short),
805 typeof(ushort),
806 typeof(int),
807 typeof(uint),
808 typeof(long),
809 typeof(ulong),
810 typeof(char),
811 typeof(float),
812 typeof(decimal),
813 }
814 },
815 {
816 typeof(decimal),
817 new HashSet<Type>()
818 {
819 typeof(sbyte),
820 typeof(byte),
821 typeof(short),
822 typeof(ushort),
823 typeof(int),
824 typeof(uint),
825 typeof(long),
826 typeof(ulong),
827 typeof(char),
828 typeof(float),
829 typeof(double)
830 }
831 }
832 };
833
834 #endregion
835 }
836}