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

zorbathut / dec / 9307326907

30 May 2024 06:18PM UTC coverage: 91.303% (-0.1%) from 91.401%
9307326907

Pull #7

github

zorbathut
Decs can now include `class` tags, if you need to specify a type that doesn't work as an XML node name.
Pull Request #7: Decs can now include class tags, if you need to specify a type that doesn't work as an XML node name.

4336 of 4749 relevant lines covered (91.3%)

183722.72 hits per line

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

98.2
/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,029✔
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,029✔
53
            {
1,515✔
54
                StrippedTypeCache = AppDomain.CurrentDomain.GetAssemblies()
1,515✔
55
                    .SelectMany(asm => {
566,734✔
56
                        try
1,515✔
57
                        {
566,734✔
58
                            return asm.GetTypes();
566,734✔
59
                        }
1,515✔
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,515✔
63
                            // To test this, we'd have to create a fake .dll that existed just to trigger this issue.
1,515✔
64
                            return reflectionException.Types.Where(t => t != null);
×
65
                        }
1,515✔
66
                    })
566,734✔
67
                    .Where(t => t.DeclaringType == null)    // we have to split these up anyway, so including declaring types just makes our life a little harder
23,653,588✔
68
                    .Distinct()
1,515✔
69
                    .GroupBy(t => {
14,958,212✔
70
                        if (t.IsGenericType)
14,958,212✔
71
                        {
817,258✔
72
                            return (t.FullName.Substring(0, t.FullName.IndexOfUnbounded('`')), t.GetGenericArguments().Length);
817,258✔
73
                        }
1,515✔
74
                        else
1,515✔
75
                        {
14,142,469✔
76
                            return (t.FullName, 0);
14,142,469✔
77
                        }
1,515✔
78
                    })
14,958,212✔
79
                    .ToDictionary(
1,515✔
80
                        group => group.Key,
14,150,194✔
81
                        group => group.ToArray());
14,150,194✔
82
            }
1,515✔
83

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

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

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

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

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

120
            Type chosenType;
121
            if (addedTypes == 0)
1,936✔
122
            {
1,811✔
123
                chosenType = root.GetNestedType(token, BindingFlags.Public | BindingFlags.NonPublic);
1,811✔
124
            }
1,811✔
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,936✔
132
        }
4,227✔
133

134
        internal static bool ParsePiece(string input, InputContext context, out int endIndex, out string name, ref List<Type> types)
135
        {
8,015✔
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,015✔
138
            name = input.Substring(0, nameEnd);
8,015✔
139

140
            // If we have a < we need to extract generic arguments.
141
            if (nameEnd < input.Length && input[nameEnd] == '<')
8,015✔
142
            {
716✔
143
                if (!ParseGenericParams(input.Substring(nameEnd + 1), context, out int endOfGenericsAdjustment, ref types))
716✔
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 <>.
676✔
151
                // . . . but also, make sure we don't have a trailing dot!
152
                if (endIndex == input.Length && input[endIndex - 1] == '.')
676✔
153
                {
10✔
154
                    Dbg.Err($"{context}: Type containing `{input}` has trailing .");
10✔
155
                    return false;
10✔
156
                }
157
            }
666✔
158
            else
159
            {
7,299✔
160
                endIndex = nameEnd + 1; // adjustment for .
7,299✔
161
            }
7,299✔
162

163
            return true;
7,965✔
164
        }
8,015✔
165

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

172
            int typeStart = 0;
716✔
173

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

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

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

200
                if (depth < 0)
6,247✔
201
                {
706✔
202
                    break;
706✔
203
                }
204
            }
5,541✔
205

206
            if (depth != -1)
716✔
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] != '.')
706✔
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)
676✔
219
            {
646✔
220
                if (types == null)
646✔
221
                {
446✔
222
                    types = new List<Type>();
446✔
223
                }
446✔
224
                types.Add(UtilType.ParseDecFormatted(tstring.Substring(typeStart, endIndex - typeStart).Trim(), context));
646✔
225
            }
646✔
226

227
            return true;
676✔
228
        }
716✔
229

230
        private static Type ParseIndependentType(string text, InputContext context)
231
        {
3,031✔
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,031✔
240
            string currentPrefix = "";
3,031✔
241
            while (nextTokenEnd < text.Length)
6,544✔
242
            {
6,054✔
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,054✔
246
                if (!ParsePiece(text.Substring(nextTokenEnd), context, out int currentTokenLength, out string tokenText, ref genericParameters))
6,054✔
247
                {
25✔
248
                    // parse error, abort
249
                    return null;
25✔
250
                }
251

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

255
                // update our currentPrefix
256
                if (currentPrefix.Length == 0)
6,029✔
257
                {
3,011✔
258
                    currentPrefix = tokenText;
3,011✔
259
                }
3,011✔
260
                else
261
                {
3,018✔
262
                    currentPrefix += "." + tokenText;
3,018✔
263
                }
3,018✔
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,029✔
267

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

279
                // We did not! Continue on.
280
                if (genericParameters != null)
3,738✔
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,513✔
286

287
            // Ran out of string, no match.
288
            return null;
490✔
289
        }
3,031✔
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
        {
461,257✔
295
            if (text == "")
461,257✔
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("{"))
461,252✔
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))
461,252✔
311
            {
445,926✔
312
                if (cacheVal == null)
445,926✔
313
                {
5✔
314
                    Dbg.Err($"{context}: Repeating previous failure to parse type named `{text}`");
5✔
315
                }
5✔
316
                return cacheVal;
445,926✔
317
            }
318

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

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

339
            // 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.
340
            var possibleTypes = Config.UsingNamespaces
2,231✔
341
                .Select(ns => ParseIndependentType($"{ns}.{text}", context))
3,031✔
342
                .Concat(ParseIndependentType(text, context))
2,231✔
343
                .Where(t => t != null)
5,262✔
344
                .ToArray();
2,231✔
345

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

362
            if (arrayRanks == 1)
2,231✔
363
            {
60✔
364
                // I'm not totally sure why MakeArrayType(1) does the wrong thing here
365
                result = result.MakeArrayType();
60✔
366
            }
60✔
367
            else if (arrayRanks > 1)
2,171✔
368
            {
10✔
369
                result = result.MakeArrayType(arrayRanks);
10✔
370
            }
10✔
371

372
            ParseCache[text] = result;
2,231✔
373
            return result;
2,231✔
374
        }
461,257✔
375

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

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

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

407
                string baseString = baseType.FullName.Replace("+", ".");
1,681✔
408
                string bestPrefix = "";
1,681✔
409
                foreach (var prefix in Config.UsingNamespaces)
5,763✔
410
                {
360✔
411
                    string prospective = prefix + ".";
360✔
412
                    if (bestPrefix.Length < prospective.Length && baseString.StartsWith(prospective))
360✔
413
                    {
305✔
414
                        bestPrefix = prospective;
305✔
415
                    }
305✔
416
                }
360✔
417

418
                baseString = baseString.Remove(0, bestPrefix.Length);
1,681✔
419

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

438
                ComposeDecCache[type] = result;
1,681✔
439
                return result;
1,681✔
440
            }
441
        }
222,142✔
442

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

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

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

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

470
            ComposeCSCache[type] = result;
2,670✔
471

472
            return result;
2,670✔
473
        }
9,565✔
474

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

482
            // Seed with our primitive types
483
            for (int i = 0; i < PrimitiveTypes.Length; ++i)
857,480✔
484
            {
403,520✔
485
                ComposeDecCache[PrimitiveTypes[i].type] = PrimitiveTypes[i].str;
403,520✔
486
                ParseCache[PrimitiveTypes[i].str] = PrimitiveTypes[i].type;
403,520✔
487
            }
403,520✔
488
        }
25,220✔
489

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

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

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

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

526
                    result = DecDatabaseStatus.Abstract;
55✔
527
                }
55✔
528
                else if (type.BaseType.GetCustomAttribute<AbstractAttribute>(false) != null)
930✔
529
                {
825✔
530
                    // 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.
531
                    GetDecDatabaseStatus(type.BaseType);
825✔
532

533
                    result = DecDatabaseStatus.Root;
825✔
534
                }
825✔
535
                else
536
                {
105✔
537
                    // 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.
538
                    GetDecDatabaseStatus(type.BaseType);
105✔
539

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

544
                GetDecDatabaseStatusCache.Add(type, result);
990✔
545
            }
990✔
546

547
            return result;
62,200✔
548
        }
62,200✔
549

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

568
                    result = currentType;
920✔
569
                }
920✔
570

571
                GetDecRootTypeCache.Add(type, result);
950✔
572
            }
950✔
573

574
            return result;
113,210✔
575
        }
113,210✔
576

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

586
        internal enum ParseModeCategory
587
        {
588
            Dec,
589
            Object,
590
            OrderedContainer,
591
            UnorderedContainer,
592
            Value,
593
        }
594
        internal static ParseModeCategory CalculateSerializationModeCategory(this Type type, Converter converter, bool isRootDec)
595
        {
1,367,464✔
596
            if (isRootDec && typeof(Dec).IsAssignableFrom(type))
1,367,464✔
597
            {
×
598
                return ParseModeCategory.Dec;
×
599
            }
600
            else if (false
1,367,464✔
601
                || type.IsPrimitive
1,367,464✔
602
                || type == typeof(string)
1,367,464✔
603
                || type == typeof(Type)
1,367,464✔
604
                || (!isRootDec && typeof(Dec).IsAssignableFrom(type)) // unnecessary isRootDec test here is to shortcut the expensive IsAssignableFrom call
1,367,464✔
605
                || typeof(Enum).IsAssignableFrom(type)
1,367,464✔
606
                || converter is ConverterString
1,367,464✔
607
                || (System.ComponentModel.TypeDescriptor.GetConverter(type)?.CanConvertFrom(typeof(string)) ?? false)   // this is last because it's slow
1,367,464✔
608
            )
1,367,464✔
609
            {
393,856✔
610
                return ParseModeCategory.Value;
393,856✔
611
            }
612
            else if (
973,608✔
613
                (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) ||
973,608✔
614
                type.IsArray ||
973,608✔
615
                (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Stack<>)) ||
973,608✔
616
                (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Queue<>))
973,608✔
617
            )
973,608✔
618
            {
65,385✔
619
                return ParseModeCategory.OrderedContainer;
65,385✔
620
            }
621
            else if (
908,223✔
622
                (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) ||
908,223✔
623
                (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(HashSet<>))
908,223✔
624
            )
908,223✔
625
            {
35,555✔
626
                return ParseModeCategory.UnorderedContainer;
35,555✔
627
            }
628
            else if (type.IsGenericType && (
872,668✔
629
                    type.GetGenericTypeDefinition() == typeof(Tuple<>) ||
872,668✔
630
                    type.GetGenericTypeDefinition() == typeof(Tuple<,>) ||
872,668✔
631
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,>) ||
872,668✔
632
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,,>) ||
872,668✔
633
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,,,>) ||
872,668✔
634
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,,,,>) ||
872,668✔
635
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,,,,,>) ||
872,668✔
636
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,,,,,,>) ||
872,668✔
637
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<>) ||
872,668✔
638
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,>) ||
872,668✔
639
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,>) ||
872,668✔
640
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,,>) ||
872,668✔
641
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,>) ||
872,668✔
642
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,>) ||
872,668✔
643
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,,>) ||
872,668✔
644
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,,,>)
872,668✔
645
                ))
872,668✔
646
            {
1,050✔
647
                return ParseModeCategory.Value;
1,050✔
648
            }
649
            else
650
            {
871,618✔
651
                return ParseModeCategory.Object;
871,618✔
652
            }
653
        }
1,367,464✔
654
    }
655
}
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