• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zorbathut / dec / 10333421008

10 Aug 2024 05:50PM UTC coverage: 91.269% (+0.03%) from 91.242%
10333421008

push

github

zorbathut
Fix: Cloning an object with a factory that depends on a non-shared recorded object can, if the stack depth is too deep, result in the non-shared recorded object not yet being ready.

4401 of 4822 relevant lines covered (91.27%)

198133.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.21
/src/UtilType.cs
1
namespace Dec
2
{
3
    using System;
4
    using System.Collections.Generic;
5
    using System.Linq;
6
    using System.Reflection;
7
    using System.Text.RegularExpressions;
8

9
    internal static class UtilType
10
    {
11
        // Our Official Type Format:
12
        // Namespaces are separated by .'s, for example, LowerNamespace.UpperNamespace.ClassName
13
        // Member classes are also separted by .'s, which means you can have LowerNamespace.ClassName.MemberClass
14
        // C# wants to do those with +'s, but we're using .'s for readability and XML compatibility reasons.
15
        // Generics currently use <>'s as you would expect. This isn't compatible with XML tags but I'm just kind of living with it for now.
16
        // And yes, this also isn't compatible with C#'s internal types.
17

18
        // When serializing types, we chop off as much of the prefix as we can. When deserializing types, we error if there's ambiguity based on existing prefixes.
19

20
        private struct PrimitiveTypeLookup
21
        {
22
            public Type type;
23
            public string str;
24
        }
25
        private static readonly PrimitiveTypeLookup[] PrimitiveTypes = new PrimitiveTypeLookup[]
10✔
26
        {
10✔
27
            new PrimitiveTypeLookup { type = typeof(bool), str = "bool" },
10✔
28
            new PrimitiveTypeLookup { type = typeof(int), str = "int" },
10✔
29
            new PrimitiveTypeLookup { type = typeof(byte), str = "byte" },
10✔
30
            new PrimitiveTypeLookup { type = typeof(sbyte), str = "sbyte" },
10✔
31
            new PrimitiveTypeLookup { type = typeof(char), str = "char" },
10✔
32
            new PrimitiveTypeLookup { type = typeof(decimal), str = "decimal" },
10✔
33
            new PrimitiveTypeLookup { type = typeof(double), str = "double" },
10✔
34
            new PrimitiveTypeLookup { type = typeof(float), str = "float" },
10✔
35
            new PrimitiveTypeLookup { type = typeof(int), str = "int" },
10✔
36
            new PrimitiveTypeLookup { type = typeof(uint), str = "uint" },
10✔
37
            new PrimitiveTypeLookup { type = typeof(long), str = "long" },
10✔
38
            new PrimitiveTypeLookup { type = typeof(ulong), str = "ulong" },
10✔
39
            new PrimitiveTypeLookup { type = typeof(short), str = "short" },
10✔
40
            new PrimitiveTypeLookup { type = typeof(ushort), str = "ushort" },
10✔
41
            new PrimitiveTypeLookup { type = typeof(object), str = "object" },
10✔
42
            new PrimitiveTypeLookup { type = typeof(string), str = "string" },
10✔
43
        };
10✔
44

45
        private static Regex GenericParameterMatcher = new Regex("`[0-9]+", RegexOptions.Compiled);
10✔
46
        private static Dictionary<(string, int), Type[]> StrippedTypeCache = null;  // this is fine not being concurrent; it's set once and then never modified
10✔
47
        private static Type GetTypeFromAnyAssembly(string text, int gparams, InputContext context)
48
        {
6,629✔
49
            // This is technically unnecessary if we're not parsing a generic, but we may as well do it because the cache will still be faster for nongenerics.
50
            // If we really wanted a perf boost here, we'd do one pass for non-generic objects, then do it again on a cache miss to fill it with generic stuff.
51
            // But there's probably much better performance boosts to be seen throughout this.
52
            if (StrippedTypeCache == null)
6,629✔
53
            {
1,635✔
54
                StrippedTypeCache = AppDomain.CurrentDomain.GetAssemblies()
1,635✔
55
                    .SelectMany(asm => {
607,725✔
56
                        try
1,635✔
57
                        {
607,725✔
58
                            return asm.GetTypes();
607,725✔
59
                        }
1,635✔
60
                        catch (ReflectionTypeLoadException reflectionException)
×
61
                        {
×
62
                            // This is very difficult to code-coverage - it happens on some platforms sometimes, but not on our automatic test server.
1,635✔
63
                            // To test this, we'd have to create a fake .dll that existed just to trigger this issue.
1,635✔
64
                            return reflectionException.Types.Where(t => t != null);
×
65
                        }
1,635✔
66
                    })
607,725✔
67
                    .Where(t => t.DeclaringType == null)    // we have to split these up anyway, so including declaring types just makes our life a little harder
25,522,802✔
68
                    .Distinct()
1,635✔
69
                    .GroupBy(t => {
16,142,622✔
70
                        if (t.IsGenericType)
16,142,622✔
71
                        {
882,034✔
72
                            return (t.FullName.Substring(0, t.FullName.IndexOfUnbounded('`')), t.GetGenericArguments().Length);
882,034✔
73
                        }
1,635✔
74
                        else
1,635✔
75
                        {
15,262,223✔
76
                            return (t.FullName, 0);
15,262,223✔
77
                        }
1,635✔
78
                    })
16,142,622✔
79
                    .ToDictionary(
1,635✔
80
                        group => group.Key,
15,273,854✔
81
                        group => group.ToArray());
15,273,854✔
82
            }
1,635✔
83

84
            var result = StrippedTypeCache.TryGetValue((text, gparams));
6,629✔
85

86
            if (result == null)
6,629✔
87
            {
4,173✔
88
                return null;
4,173✔
89
            }
90
            else if (result.Length == 1)
2,456✔
91
            {
2,456✔
92
                return result[0];
2,456✔
93
            }
94
            else
95
            {
×
96
                Dbg.Err($"{context}: Too many types found with name {text}");
×
97
                return result[0];
×
98
            }
99
        }
6,629✔
100

101
        private static Type ParseSubtype(Type root, string text, ref List<Type> genericTypes, InputContext context)
102
        {
4,442✔
103
            if (root == null)
4,442✔
104
            {
180✔
105
                return null;
180✔
106
            }
107

108
            if (text.Length == 0)
4,262✔
109
            {
2,251✔
110
                return root;
2,251✔
111
            }
112

113
            int previousTypes = genericTypes?.Count ?? 0;
2,011✔
114
            if (!ParsePiece(text, context, out int endIndex, out string token, ref genericTypes))
2,011✔
115
            {
25✔
116
                return null;
25✔
117
            }
118
            int addedTypes = (genericTypes?.Count ?? 0) - previousTypes;
1,986✔
119

120
            Type chosenType;
121
            if (addedTypes == 0)
1,986✔
122
            {
1,861✔
123
                chosenType = root.GetNestedType(token, BindingFlags.Public | BindingFlags.NonPublic);
1,861✔
124
            }
1,861✔
125
            else
126
            {
125✔
127
                chosenType = root.GetNestedType($"{token}`{addedTypes}", BindingFlags.Public | BindingFlags.NonPublic);
125✔
128
            }
125✔
129

130
            // Chain on to another call in case we have a further-nested class-of-class
131
            return ParseSubtype(chosenType, text.SubstringSafe(endIndex), ref genericTypes, context);
1,986✔
132
        }
4,442✔
133

134
        internal static bool ParsePiece(string input, InputContext context, out int endIndex, out string name, ref List<Type> types)
135
        {
8,665✔
136
            // Isolate the first token; this is the distance from our current index to the first . or < that *isn't* the beginning of a class name.
137
            int nameEnd = Math.Min(input.IndexOfUnbounded('.'), input.IndexOfUnbounded('<', 1));
8,665✔
138
            name = input.Substring(0, nameEnd);
8,665✔
139

140
            // If we have a < we need to extract generic arguments.
141
            if (nameEnd < input.Length && input[nameEnd] == '<')
8,665✔
142
            {
731✔
143
                if (!ParseGenericParams(input.Substring(nameEnd + 1), context, out int endOfGenericsAdjustment, ref types))
731✔
144
                {
40✔
145
                    // just kinda give up to ensure we don't get trapped in a loop
146
                    Dbg.Err($"{context}: Failed to parse generic arguments for type containing {input}");
40✔
147
                    endIndex = input.Length;
40✔
148
                    return false;
40✔
149
                }
150
                endIndex = nameEnd + endOfGenericsAdjustment + 3; // adjustment for <>.
691✔
151
                // . . . but also, make sure we don't have a trailing dot!
152
                if (endIndex == input.Length && input[endIndex - 1] == '.')
691✔
153
                {
10✔
154
                    Dbg.Err($"{context}: Type containing `{input}` has trailing .");
10✔
155
                    return false;
10✔
156
                }
157
            }
681✔
158
            else
159
            {
7,934✔
160
                endIndex = nameEnd + 1; // adjustment for .
7,934✔
161
            }
7,934✔
162

163
            return true;
8,615✔
164
        }
8,665✔
165

166
        // returns false on error
167
        internal static bool ParseGenericParams(string tstring, InputContext context, out int endIndex, ref List<Type> types)
168
        {
731✔
169
            int depth = 0;
731✔
170
            endIndex = 0;
731✔
171

172
            int typeStart = 0;
731✔
173

174
            for (endIndex = 0; endIndex < tstring.Length; ++endIndex)
12,714✔
175
            {
6,347✔
176
                char c = tstring[endIndex];
6,347✔
177
                switch (c)
6,347✔
178
                {
179
                    case '<':
180
                        depth++;
75✔
181
                        break;
75✔
182

183
                    case '>':
184
                        depth--;
796✔
185
                        break;
796✔
186

187
                    case ',':
188
                        if (depth == 0)
205✔
189
                        {
190✔
190
                            if (types == null)
190✔
191
                            {
165✔
192
                                types = new List<Type>();
165✔
193
                            }
165✔
194
                            types.Add(UtilType.ParseDecFormatted(tstring.Substring(typeStart, endIndex - typeStart).Trim(), context));
190✔
195
                            typeStart = endIndex + 1;
190✔
196
                        }
190✔
197
                        break;
205✔
198
                }
199

200
                if (depth < 0)
6,347✔
201
                {
721✔
202
                    break;
721✔
203
                }
204
            }
5,626✔
205

206
            if (depth != -1)
731✔
207
            {
10✔
208
                Dbg.Err($"{context}: Mismatched angle brackets when parsing generic type component `{tstring}`");
10✔
209
                return false;
10✔
210
            }
211

212
            if (endIndex + 1 < tstring.Length && tstring[endIndex + 1] != '.')
721✔
213
            {
30✔
214
                Dbg.Err($"{context}: Unexpected character after end of generic type definition");
30✔
215
                return false;
30✔
216
            }
217

218
            if (endIndex != typeStart)
691✔
219
            {
661✔
220
                if (types == null)
661✔
221
                {
456✔
222
                    types = new List<Type>();
456✔
223
                }
456✔
224
                types.Add(UtilType.ParseDecFormatted(tstring.Substring(typeStart, endIndex - typeStart).Trim(), context));
661✔
225
            }
661✔
226

227
            return true;
691✔
228
        }
731✔
229

230
        private static Type ParseIndependentType(string text, InputContext context)
231
        {
3,316✔
232
            // This function just tries to find a class with a specific namespace; we no longer worry about `using`.
233
            // Our challenge is to find a function with the right ordering of generic arguments. NS.Foo`1.Bar is different from NS.Foo.Bar`1, for example.
234
            // We're actually going to transform our input into C#'s generic-argument layout so we can find the right instance easily.
235

236
            // This is complicated by the fact that the compiler can generate class names with <> in them - that is, the *name*, not the generic specialization.
237
            // As an added bonus, the namespace is signaled differently, so we're going to be chopping this up awkwardly as we do it.
238

239
            int nextTokenEnd = 0;
3,316✔
240
            string currentPrefix = "";
3,316✔
241
            while (nextTokenEnd < text.Length)
7,264✔
242
            {
6,654✔
243
                // Parse another chunk
244
                // This involves an unnecessary amount of copying - this should be fixed, but this is currently not the bottleneck anywhere I've seen.
245
                List<Type> genericParameters = null; // avoid churn if we can
6,654✔
246
                if (!ParsePiece(text.Substring(nextTokenEnd), context, out int currentTokenLength, out string tokenText, ref genericParameters))
6,654✔
247
                {
25✔
248
                    // parse error, abort
249
                    return null;
25✔
250
                }
251

252
                // update next token position
253
                nextTokenEnd += currentTokenLength;
6,629✔
254

255
                // update our currentPrefix
256
                if (currentPrefix.Length == 0)
6,629✔
257
                {
3,296✔
258
                    currentPrefix = tokenText;
3,296✔
259
                }
3,296✔
260
                else
261
                {
3,333✔
262
                    currentPrefix += "." + tokenText;
3,333✔
263
                }
3,333✔
264

265
                // This is the thing we're going to test to see if is a class.
266
                var parsedType = GetTypeFromAnyAssembly(currentPrefix, genericParameters?.Count ?? 0, context);
6,629✔
267

268
                if (parsedType != null)
6,629✔
269
                {
2,456✔
270
                    // We found the root! Keep on digging.
271
                    Type primitiveType = ParseSubtype(parsedType, text.SubstringSafe(nextTokenEnd), ref genericParameters, context);
2,456✔
272
                    if (primitiveType != null && genericParameters != null)
2,456✔
273
                    {
371✔
274
                        primitiveType = primitiveType.MakeGenericType(genericParameters.ToArray());
371✔
275
                    }
371✔
276
                    return primitiveType;
2,456✔
277
                }
278

279
                // We did not! Continue on.
280
                if (genericParameters != null)
4,173✔
281
                {
225✔
282
                    // . . . but we can't go past a set of generics, namespace generics aren't a thing. So we're done.
283
                    return null;
225✔
284
                }
285
            }
3,948✔
286

287
            // Ran out of string, no match.
288
            return null;
610✔
289
        }
3,316✔
290

291
        private static Regex ArrayRankParser = new Regex(@"\[([,]*)\]$", RegexOptions.Compiled);
10✔
292
        private static Dictionary<string, Type> ParseCache = new Dictionary<string, Type>();
10✔
293
        internal static Type ParseDecFormatted(string text, InputContext context)
294
        {
462,292✔
295
            if (text == "")
462,292✔
296
            {
5✔
297
                Dbg.Err($"{context}: The empty string is not a valid type");
5✔
298
                return null;
5✔
299
            }
300

301
            if (text.Contains("{"))
462,287✔
302
            {
25✔
303
                // There's a bunch of times you might want to put full template typenames in XML and doing so is a pain due to XML treating angled brackets specially
304
                // so we allow for {} in its place
305
                // "why not ()" because that's tuple syntax
306
                // "why not []" because that's array syntax
307
                text = text.Replace("{", "<").Replace("}", ">");
25✔
308
            }
25✔
309

310
            if (ParseCache.TryGetValue(text, out Type cacheVal))
462,287✔
311
            {
446,796✔
312
                if (cacheVal == null)
446,796✔
313
                {
5✔
314
                    Dbg.Err($"{context}: Repeating previous failure to parse type named `{text}`");
5✔
315
                }
5✔
316
                return cacheVal;
446,796✔
317
            }
318

319
            if (Config.TestParameters?.explicitTypes != null)
15,491✔
320
            {
14,481✔
321
                // Test override, we check the test types first
322
                foreach (var explicitType in Config.TestParameters.explicitTypes)
174,434✔
323
                {
72,033✔
324
                    if (text == explicitType.Name)
72,033✔
325
                    {
13,075✔
326
                        ParseCache[text] = explicitType;
13,075✔
327
                        return explicitType;
13,075✔
328
                    }
329
                }
58,958✔
330
            }
1,406✔
331

332
            int arrayRanks = 0;
2,416✔
333
            string originalText = text;
2,416✔
334
            if (ArrayRankParser.Match(text) is Match match && match.Success)
2,416✔
335
            {
60✔
336
                arrayRanks = match.Groups[1].Length + 1;
60✔
337
                text = text.Substring(0, text.Length - match.Length);
60✔
338
            }
60✔
339

340
            // We need to find a class that matches the least number of tokens. Namespaces can't be generics so at most this continues until we hit a namespace.
341
            var possibleTypes = Config.UsingNamespaces
2,416✔
342
                .Select(ns => ParseIndependentType($"{ns}.{text}", context))
3,316✔
343
                .Concat(ParseIndependentType(text, context))
2,416✔
344
                .Where(t => t != null)
5,732✔
345
                .ToArray();
2,416✔
346

347
            Type result;
348
            if (possibleTypes.Length == 0)
2,416✔
349
            {
200✔
350
                Dbg.Err($"{context}: Couldn't find type named `{text}`");
200✔
351
                result = null;
200✔
352
            }
200✔
353
            else if (possibleTypes.Length > 1)
2,216✔
354
            {
35✔
355
                Dbg.Err($"{context}: Found too many types named `{text}` ({possibleTypes.Select(t => t.FullName).ToCommaString()})");
105✔
356
                result = possibleTypes[0];
35✔
357
            }
35✔
358
            else
359
            {
2,181✔
360
                result = possibleTypes[0];
2,181✔
361
            }
2,181✔
362

363
            if (arrayRanks == 1)
2,416✔
364
            {
55✔
365
                // I'm not totally sure why MakeArrayType(1) does the wrong thing here
366
                result = result.MakeArrayType();
55✔
367
            }
55✔
368
            else if (arrayRanks > 1)
2,361✔
369
            {
5✔
370
                result = result.MakeArrayType(arrayRanks);
5✔
371
            }
5✔
372

373
            ParseCache[originalText] = result;
2,416✔
374
            return result;
2,416✔
375
        }
462,292✔
376

377
        private static readonly Regex GenericParameterReplacementRegex = new Regex(@"`\d+", RegexOptions.Compiled);
10✔
378
        private static Dictionary<Type, string> ComposeDecCache = new Dictionary<Type, string>();
10✔
379
        internal static string ComposeDecFormatted(this Type type)
380
        {
222,687✔
381
            if (ComposeDecCache.TryGetValue(type, out string cacheVal))
222,687✔
382
            {
216,731✔
383
                return cacheVal;
216,731✔
384
            }
385

386
            if (Config.TestParameters?.explicitTypes != null)
5,956✔
387
            {
5,121✔
388
                // Test override, we check the test types first
389
                foreach (var explicitType in Config.TestParameters.explicitTypes)
58,649✔
390
                {
23,718✔
391
                    if (type == explicitType)
23,718✔
392
                    {
4,150✔
393
                        string result = explicitType.Name;
4,150✔
394
                        ComposeDecCache[type] = result;
4,150✔
395
                        return result;
4,150✔
396
                    }
397
                }
19,568✔
398
            }
971✔
399

400
            {
1,806✔
401
                // Main parsing
402
                Type baseType = type;
1,806✔
403
                if (type.IsConstructedGenericType)
1,806✔
404
                {
326✔
405
                    baseType = type.GetGenericTypeDefinition();
326✔
406
                }
326✔
407

408
                string baseString = baseType.FullName.Replace("+", ".");
1,806✔
409
                string bestPrefix = "";
1,806✔
410
                foreach (var prefix in Config.UsingNamespaces)
6,178✔
411
                {
380✔
412
                    string prospective = prefix + ".";
380✔
413
                    if (bestPrefix.Length < prospective.Length && baseString.StartsWith(prospective))
380✔
414
                    {
325✔
415
                        bestPrefix = prospective;
325✔
416
                    }
325✔
417
                }
380✔
418

419
                baseString = baseString.Remove(0, bestPrefix.Length);
1,806✔
420

421
                string result;
422
                if (type.IsConstructedGenericType)
1,806✔
423
                {
326✔
424
                    var genericTypes = type.GenericTypeArguments.Select(t => t.ComposeDecFormatted()).ToList();
812✔
425
                    int paramIndex = 0;
326✔
426
                    result = GenericParameterReplacementRegex.Replace(baseString, match =>
326✔
427
                        {
692✔
428
                            int numParams = int.Parse(match.Value.Substring(1));
692✔
429
                            var replacement = string.Join(", ", genericTypes.GetRange(paramIndex, numParams));
692✔
430
                            paramIndex += numParams;
692✔
431
                            return $"<{replacement}>";
692✔
432
                        });
692✔
433
                }
326✔
434
                else
435
                {
1,480✔
436
                    result = baseString;
1,480✔
437
                }
1,480✔
438

439
                ComposeDecCache[type] = result;
1,806✔
440
                return result;
1,806✔
441
            }
442
        }
222,687✔
443

444
        private static Dictionary<Type, string> ComposeCSCache = new Dictionary<Type, string>();
10✔
445
        internal static string ComposeCSFormatted(this Type type)
446
        {
9,570✔
447
            if (ComposeCSCache.TryGetValue(type, out string cacheVal))
9,570✔
448
            {
6,895✔
449
                return cacheVal;
6,895✔
450
            }
451

452
            string result;
453
            if (type.IsConstructedGenericType)
2,675✔
454
            {
95✔
455
                result = type.GetGenericTypeDefinition().ToString();
95✔
456
                result = result.Substring(0, result.IndexOf('`'));
95✔
457
            }
95✔
458
            else
459
            {
2,580✔
460
                result = type.ToString();
2,580✔
461
            }
2,580✔
462

463
            // strip out the namespace/class distinction
464
            result = result.Replace('+', '.');
2,675✔
465

466
            if (type.IsConstructedGenericType)
2,675✔
467
            {
95✔
468
                result += "<" + string.Join(", ", type.GetGenericArguments().Select(arg => ComposeCSFormatted(arg))) + ">";
220✔
469
            }
95✔
470

471
            ComposeCSCache[type] = result;
2,675✔
472

473
            return result;
2,675✔
474
        }
9,570✔
475

476
        internal static void ClearCache()
477
        {
26,130✔
478
            ComposeDecCache.Clear();
26,130✔
479
            ComposeCSCache.Clear();
26,130✔
480
            ParseCache.Clear();
26,130✔
481
            StrippedTypeCache = null;
26,130✔
482

483
            // Seed with our primitive types
484
            for (int i = 0; i < PrimitiveTypes.Length; ++i)
888,420✔
485
            {
418,080✔
486
                ComposeDecCache[PrimitiveTypes[i].type] = PrimitiveTypes[i].str;
418,080✔
487
                ParseCache[PrimitiveTypes[i].str] = PrimitiveTypes[i].type;
418,080✔
488
            }
418,080✔
489
        }
26,130✔
490

491
        static UtilType()
492
        {
10✔
493
            // seed the cache
494
            ClearCache();
10✔
495
        }
10✔
496

497
        internal enum DecDatabaseStatus
498
        {
499
            Invalid,
500
            Abstract,
501
            Root,
502
            Branch,
503
        }
504
        private static Dictionary<Type, DecDatabaseStatus> GetDecDatabaseStatusCache = new Dictionary<Type, DecDatabaseStatus>();
10✔
505
        internal static DecDatabaseStatus GetDecDatabaseStatus(this Type type)
506
        {
62,275✔
507
            if (!GetDecDatabaseStatusCache.TryGetValue(type, out var result))
62,275✔
508
            {
985✔
509
                if (!typeof(Dec).IsAssignableFrom(type))
985✔
510
                {
5✔
511
                    Dbg.Err($"Queried the dec hierarchy status of a type {type} that doesn't even inherit from Dec.");
5✔
512

513
                    result = DecDatabaseStatus.Invalid;
5✔
514
                }
5✔
515
                else if (type.GetCustomAttribute<AbstractAttribute>(false) != null)
980✔
516
                {
55✔
517
                    if (!type.IsAbstract)
55✔
518
                    {
10✔
519
                        Dbg.Err($"Type {type} is tagged Dec.Abstract, but is not abstract.");
10✔
520
                    }
10✔
521

522
                    if (type.BaseType != typeof(object) && GetDecDatabaseStatus(type.BaseType) > DecDatabaseStatus.Abstract)
55✔
523
                    {
15✔
524
                        Dbg.Err($"Type {type} is tagged Dec.Abstract, but inherits from {type.BaseType} which is within the database.");
15✔
525
                    }
15✔
526

527
                    result = DecDatabaseStatus.Abstract;
55✔
528
                }
55✔
529
                else if (type.BaseType.GetCustomAttribute<AbstractAttribute>(false) != null)
925✔
530
                {
820✔
531
                    // We do this just to validate everything beneath this. It'd better return Abstract! More importantly, it goes through all parents and makes sure they're consistent.
532
                    GetDecDatabaseStatus(type.BaseType);
820✔
533

534
                    result = DecDatabaseStatus.Root;
820✔
535
                }
820✔
536
                else
537
                {
105✔
538
                    // Further validation. This time we really hope it returns Abstract or Root. More importantly, it goes through all parents and makes sure they're consistent.
539
                    GetDecDatabaseStatus(type.BaseType);
105✔
540

541
                    // Our parent isn't NotDatabaseRootAttribute. We are not a database root, but we also can't say anything meaningful about our parents.
542
                    result = DecDatabaseStatus.Branch;
105✔
543
                }
105✔
544

545
                GetDecDatabaseStatusCache.Add(type, result);
985✔
546
            }
985✔
547

548
            return result;
62,275✔
549
        }
62,275✔
550

551
        private static Dictionary<Type, Type> GetDecRootTypeCache = new Dictionary<Type, Type>();
10✔
552
        internal static Type GetDecRootType(this Type type)
553
        {
113,435✔
554
            if (!GetDecRootTypeCache.TryGetValue(type, out var result))
113,435✔
555
            {
945✔
556
                if (GetDecDatabaseStatus(type) <= DecDatabaseStatus.Abstract)
945✔
557
                {
30✔
558
                    Dbg.Err($"{type} does not exist within a database hierarchy.");
30✔
559
                    result = null;
30✔
560
                }
30✔
561
                else
562
                {
915✔
563
                    Type currentType = type;
915✔
564
                    while (GetDecDatabaseStatus(currentType) == DecDatabaseStatus.Branch)
1,035✔
565
                    {
120✔
566
                        currentType = currentType.BaseType;
120✔
567
                    }
120✔
568

569
                    result = currentType;
915✔
570
                }
915✔
571

572
                GetDecRootTypeCache.Add(type, result);
945✔
573
            }
945✔
574

575
            return result;
113,435✔
576
        }
113,435✔
577

578
        internal static bool CanBeShared(this Type type)
579
        {
2,266,814✔
580
            return Util.CanBeShared(type);
2,266,814✔
581
        }
2,266,814✔
582
        internal static bool CanBeCloneCopied(this Type type)
583
        {
1,265✔
584
            return type.IsPrimitive || typeof(Dec).IsAssignableFrom(type) || type == typeof(string) || type == typeof(Type) || type.GetCustomAttribute<CloneWithAssignmentAttribute>() != null;
1,265✔
585
        }
1,265✔
586

587
        internal enum ParseModeCategory
588
        {
589
            Dec,
590
            Object,
591
            OrderedContainer,
592
            UnorderedContainer,
593
            Value,
594
        }
595
        internal static ParseModeCategory CalculateSerializationModeCategory(this Type type, Converter converter, bool isRootDec)
596
        {
1,510,564✔
597
            if (isRootDec && typeof(Dec).IsAssignableFrom(type))
1,510,564✔
598
            {
×
599
                return ParseModeCategory.Dec;
×
600
            }
601
            else if (false
1,510,564✔
602
                || type.IsPrimitive
1,510,564✔
603
                || type == typeof(string)
1,510,564✔
604
                || type == typeof(Type)
1,510,564✔
605
                || (!isRootDec && typeof(Dec).IsAssignableFrom(type)) // unnecessary isRootDec test here is to shortcut the expensive IsAssignableFrom call
1,510,564✔
606
                || typeof(Enum).IsAssignableFrom(type)
1,510,564✔
607
                || converter is ConverterString
1,510,564✔
608
                || (System.ComponentModel.TypeDescriptor.GetConverter(type)?.CanConvertFrom(typeof(string)) ?? false)   // this is last because it's slow
1,510,564✔
609
            )
1,510,564✔
610
            {
399,766✔
611
                return ParseModeCategory.Value;
399,766✔
612
            }
613
            else if (
1,110,798✔
614
                (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) ||
1,110,798✔
615
                type.IsArray ||
1,110,798✔
616
                (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Stack<>)) ||
1,110,798✔
617
                (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Queue<>))
1,110,798✔
618
            )
1,110,798✔
619
            {
131,030✔
620
                return ParseModeCategory.OrderedContainer;
131,030✔
621
            }
622
            else if (
979,768✔
623
                (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) ||
979,768✔
624
                (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(HashSet<>))
979,768✔
625
            )
979,768✔
626
            {
35,575✔
627
                return ParseModeCategory.UnorderedContainer;
35,575✔
628
            }
629
            else if (type.IsGenericType && (
944,193✔
630
                    type.GetGenericTypeDefinition() == typeof(Tuple<>) ||
944,193✔
631
                    type.GetGenericTypeDefinition() == typeof(Tuple<,>) ||
944,193✔
632
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,>) ||
944,193✔
633
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,,>) ||
944,193✔
634
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,,,>) ||
944,193✔
635
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,,,,>) ||
944,193✔
636
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,,,,,>) ||
944,193✔
637
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,,,,,,>) ||
944,193✔
638
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<>) ||
944,193✔
639
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,>) ||
944,193✔
640
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,>) ||
944,193✔
641
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,,>) ||
944,193✔
642
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,>) ||
944,193✔
643
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,>) ||
944,193✔
644
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,,>) ||
944,193✔
645
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,,,>)
944,193✔
646
                ))
944,193✔
647
            {
1,065✔
648
                return ParseModeCategory.Value;
1,065✔
649
            }
650
            else
651
            {
943,128✔
652
                return ParseModeCategory.Object;
943,128✔
653
            }
654
        }
1,510,564✔
655
    }
656
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc