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

zorbathut / dec / 6363226355

30 Sep 2023 01:30PM UTC coverage: 92.553% (-0.1%) from 92.677%
6363226355

push

github-ci

Ben Rog-Wilhelm
Recorder_Enumerable: Fix: Non-7.0 tests.

3418 of 3693 relevant lines covered (92.55%)

144777.42 hits per line

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

98.14
/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[]
6✔
26
        {
6✔
27
            new PrimitiveTypeLookup { type = typeof(bool), str = "bool" },
6✔
28
            new PrimitiveTypeLookup { type = typeof(int), str = "int" },
6✔
29
            new PrimitiveTypeLookup { type = typeof(byte), str = "byte" },
6✔
30
            new PrimitiveTypeLookup { type = typeof(sbyte), str = "sbyte" },
6✔
31
            new PrimitiveTypeLookup { type = typeof(char), str = "char" },
6✔
32
            new PrimitiveTypeLookup { type = typeof(decimal), str = "decimal" },
6✔
33
            new PrimitiveTypeLookup { type = typeof(double), str = "double" },
6✔
34
            new PrimitiveTypeLookup { type = typeof(float), str = "float" },
6✔
35
            new PrimitiveTypeLookup { type = typeof(int), str = "int" },
6✔
36
            new PrimitiveTypeLookup { type = typeof(uint), str = "uint" },
6✔
37
            new PrimitiveTypeLookup { type = typeof(long), str = "long" },
6✔
38
            new PrimitiveTypeLookup { type = typeof(ulong), str = "ulong" },
6✔
39
            new PrimitiveTypeLookup { type = typeof(short), str = "short" },
6✔
40
            new PrimitiveTypeLookup { type = typeof(ushort), str = "ushort" },
6✔
41
            new PrimitiveTypeLookup { type = typeof(object), str = "object" },
6✔
42
            new PrimitiveTypeLookup { type = typeof(string), str = "string" },
6✔
43
        };
6✔
44

45
        private static Regex GenericParameterMatcher = new Regex("`[0-9]+", RegexOptions.Compiled);
6✔
46
        private static Dictionary<(string, int), Type[]> StrippedTypeCache = null;
6✔
47
        private static Type GetTypeFromAnyAssembly(string text, int gparams, InputContext context)
48
        {
3,470✔
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)
3,470✔
53
            {
885✔
54
                StrippedTypeCache = AppDomain.CurrentDomain.GetAssemblies()
885✔
55
                    .SelectMany(asm => {
332,345✔
56
                        try
885✔
57
                        {
332,345✔
58
                            return asm.GetTypes();
332,345✔
59
                        }
885✔
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.
885✔
63
                            // To test this, we'd have to create a fake .dll that existed just to trigger this issue.
885✔
64
                            return reflectionException.Types.Where(t => t != null);
×
65
                        }
885✔
66
                    })
332,345✔
67
                    .Where(t => t.DeclaringType == null)    // we have to split these up anyway, so including declaring types just makes our life a little harder
13,478,522✔
68
                    .Distinct()
885✔
69
                    .GroupBy(t => {
8,559,961✔
70
                        if (t.IsGenericType)
8,559,961✔
71
                        {
452,334✔
72
                            return (t.FullName.Substring(0, t.FullName.IndexOfUnbounded('`')), t.GetGenericArguments().Length);
452,334✔
73
                        }
885✔
74
                        else
885✔
75
                        {
8,108,512✔
76
                            return (t.FullName, 0);
8,108,512✔
77
                        }
885✔
78
                    })
8,559,961✔
79
                    .ToDictionary(
885✔
80
                        group => group.Key,
8,078,823✔
81
                        group => group.ToArray());
8,078,823✔
82
            }
885✔
83

84
            var result = StrippedTypeCache.TryGetValue((text, gparams));
3,470✔
85

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

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

108
            if (text.Length == 0)
2,359✔
109
            {
1,205✔
110
                return root;
1,205✔
111
            }
112

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

120
            Type chosenType;
121
            if (addedTypes == 0)
1,139✔
122
            {
1,064✔
123
                chosenType = root.GetNestedType(token, BindingFlags.Public | BindingFlags.NonPublic);
1,064✔
124
            }
1,064✔
125
            else
126
            {
75✔
127
                chosenType = root.GetNestedType($"{token}`{addedTypes}", BindingFlags.Public | BindingFlags.NonPublic);
75✔
128
            }
75✔
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,139✔
132
        }
2,467✔
133

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

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

164
            return true;
4,609✔
165
        }
4,639✔
166

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

173
            int typeStart = 0;
395✔
174

175
            for (endIndex = 0; endIndex < tstring.Length; ++endIndex)
6,970✔
176
            {
3,479✔
177
                char c = tstring[endIndex];
3,479✔
178
                switch (c)
3,479✔
179
                {
180
                    case '<':
181
                        depth++;
45✔
182
                        break;
45✔
183

184
                    case '>':
185
                        depth--;
434✔
186
                        break;
434✔
187

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

201
                if (depth < 0)
3,479✔
202
                {
389✔
203
                    break;
389✔
204
                }
205
            }
3,090✔
206

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

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

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

228
            return true;
371✔
229
        }
395✔
230

231
        private static Type ParseIndependentType(string text, InputContext context)
232
        {
1,739✔
233
            // This function just tries to find a class with a specific namespace; we no longer worry about `using`.
234
            // 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.
235
            // We're actually going to transform our input into C#'s generic-argument layout so we can find the right instance easily.
236

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

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

253
                // update next token position
254
                nextTokenEnd += currentTokenLength;
3,470✔
255

256
                // update our currentPrefix
257
                if (currentPrefix.Length == 0)
3,470✔
258
                {
1,727✔
259
                    currentPrefix = tokenText;
1,727✔
260
                }
1,727✔
261
                else
262
                {
1,743✔
263
                    currentPrefix += "." + tokenText;
1,743✔
264
                }
1,743✔
265

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

269
                if (parsedType != null)
3,470✔
270
                {
1,328✔
271
                    // We found the root! Keep on digging.
272
                    Type primitiveType = ParseSubtype(parsedType, text.SubstringSafe(nextTokenEnd), ref genericParameters, context);
1,328✔
273
                    if (primitiveType != null && genericParameters != null)
1,328✔
274
                    {
197✔
275
                        primitiveType = primitiveType.MakeGenericType(genericParameters.ToArray());
197✔
276
                    }
197✔
277
                    return primitiveType;
1,328✔
278
                }
279

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

288
            // Ran out of string, no match.
289
            return null;
279✔
290
        }
1,739✔
291

292
        private static Dictionary<string, Type> ParseCache = new Dictionary<string, Type>();
6✔
293
        internal static Type ParseDecFormatted(string text, InputContext context)
294
        {
339,520✔
295
            if (text == "")
339,520✔
296
            {
3✔
297
                Dbg.Err($"{context}: The empty string is not a valid type");
3✔
298
                return null;
3✔
299
            }
300

301
            if (ParseCache.TryGetValue(text, out Type cacheVal))
339,517✔
302
            {
330,536✔
303
                if (cacheVal == null)
330,536✔
304
                {
3✔
305
                    Dbg.Err($"{context}: Repeating previous failure to parse type named `{text}`");
3✔
306
                }
3✔
307
                return cacheVal;
330,536✔
308
            }
309

310
            if (Config.TestParameters?.explicitTypes != null)
8,981✔
311
            {
7,984✔
312
                // Test override, we check the test types first
313
                foreach (var explicitType in Config.TestParameters.explicitTypes)
102,475✔
314
                {
43,106✔
315
                    if (text == explicitType.Name)
43,106✔
316
                    {
7,689✔
317
                        ParseCache[text] = explicitType;
7,689✔
318
                        return explicitType;
7,689✔
319
                    }
320
                }
35,417✔
321
            }
295✔
322

323
            bool isArray = text.EndsWith("[]");
1,292✔
324
            if (isArray)
1,292✔
325
            {
45✔
326
                text = text.Substring(0, text.Length - 2);
45✔
327
            }
45✔
328

329
            // 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.
330
            var possibleTypes = Config.UsingNamespaces
1,292✔
331
                .Select(ns => ParseIndependentType($"{ns}.{text}", context))
1,739✔
332
                .Concat(ParseIndependentType(text, context))
1,292✔
333
                .Where(t => t != null)
3,031✔
334
                .ToArray();
1,292✔
335

336
            Type result;
337
            if (possibleTypes.Length == 0)
1,292✔
338
            {
108✔
339
                Dbg.Err($"{context}: Couldn't find type named `{text}`");
108✔
340
                result = null;
108✔
341
            }
108✔
342
            else if (possibleTypes.Length > 1)
1,184✔
343
            {
21✔
344
                Dbg.Err($"{context}: Found too many types named `{text}` ({possibleTypes.Select(t => t.FullName).ToCommaString()})");
63✔
345
                result = possibleTypes[0];
21✔
346
            }
21✔
347
            else
348
            {
1,163✔
349
                result = possibleTypes[0];
1,163✔
350
            }
1,163✔
351

352
            if (isArray)
1,292✔
353
            {
45✔
354
                // TODO: multiple-dimension arrays?
355
                result = result.MakeArrayType();
45✔
356
            }
45✔
357

358
            ParseCache[text] = result;
1,292✔
359
            return result;
1,292✔
360
        }
339,520✔
361

362
        private static readonly Regex GenericParameterReplacementRegex = new Regex(@"`\d+", RegexOptions.Compiled);
6✔
363
        private static Dictionary<Type, string> ComposeDecCache = new Dictionary<Type, string>();
6✔
364
        internal static string ComposeDecFormatted(this Type type)
365
        {
164,728✔
366
            if (ComposeDecCache.TryGetValue(type, out string cacheVal))
164,728✔
367
            {
161,303✔
368
                return cacheVal;
161,303✔
369
            }
370

371
            if (Config.TestParameters?.explicitTypes != null)
3,425✔
372
            {
2,576✔
373
                // Test override, we check the test types first
374
                foreach (var explicitType in Config.TestParameters.explicitTypes)
33,780✔
375
                {
14,244✔
376
                    if (type == explicitType)
14,244✔
377
                    {
2,436✔
378
                        string result = explicitType.Name;
2,436✔
379
                        ComposeDecCache[type] = result;
2,436✔
380
                        return result;
2,436✔
381
                    }
382
                }
11,808✔
383
            }
140✔
384

385
            {
989✔
386
                // Main parsing
387
                Type baseType = type;
989✔
388
                if (type.IsConstructedGenericType)
989✔
389
                {
185✔
390
                    baseType = type.GetGenericTypeDefinition();
185✔
391
                }
185✔
392

393
                string baseString = baseType.FullName.Replace("+", ".");
989✔
394
                string bestPrefix = "";
989✔
395
                foreach (var prefix in Config.UsingNamespaces)
3,375✔
396
                {
204✔
397
                    string prospective = prefix + ".";
204✔
398
                    if (bestPrefix.Length < prospective.Length && baseString.StartsWith(prospective))
204✔
399
                    {
171✔
400
                        bestPrefix = prospective;
171✔
401
                    }
171✔
402
                }
204✔
403

404
                baseString = baseString.Remove(0, bestPrefix.Length);
989✔
405

406
                string result;
407
                if (type.IsConstructedGenericType)
989✔
408
                {
185✔
409
                    var genericTypes = type.GenericTypeArguments.Select(t => t.ComposeDecFormatted()).ToList();
463✔
410
                    int paramIndex = 0;
185✔
411
                    result = GenericParameterReplacementRegex.Replace(baseString, match =>
185✔
412
                        {
394✔
413
                            int numParams = int.Parse(match.Value.Substring(1));
394✔
414
                            var replacement = string.Join(", ", genericTypes.GetRange(paramIndex, numParams));
394✔
415
                            paramIndex += numParams;
394✔
416
                            return $"<{replacement}>";
394✔
417
                        });
394✔
418
                }
185✔
419
                else
420
                {
804✔
421
                    result = baseString;
804✔
422
                }
804✔
423

424
                ComposeDecCache[type] = result;
989✔
425
                return result;
989✔
426
            }
427
        }
164,728✔
428

429
        private static Dictionary<Type, string> ComposeCSCache = new Dictionary<Type, string>();
6✔
430
        internal static string ComposeCSFormatted(this Type type)
431
        {
5,676✔
432
            if (ComposeCSCache.TryGetValue(type, out string cacheVal))
5,676✔
433
            {
4,131✔
434
                return cacheVal;
4,131✔
435
            }
436

437
            string result;
438
            if (type.IsConstructedGenericType)
1,545✔
439
            {
42✔
440
                result = type.GetGenericTypeDefinition().ToString();
42✔
441
                result = result.Substring(0, result.IndexOf('`'));
42✔
442
            }
42✔
443
            else
444
            {
1,503✔
445
                result = type.ToString();
1,503✔
446
            }
1,503✔
447

448
            // strip out the namespace/class distinction
449
            result = result.Replace('+', '.');
1,545✔
450

451
            if (type.IsConstructedGenericType)
1,545✔
452
            {
42✔
453
                result += "<" + string.Join(", ", type.GetGenericArguments().Select(arg => ComposeCSFormatted(arg))) + ">";
99✔
454
            }
42✔
455

456
            ComposeCSCache[type] = result;
1,545✔
457

458
            return result;
1,545✔
459
        }
5,676✔
460

461
        internal static void ClearCache()
462
        {
14,070✔
463
            ComposeDecCache.Clear();
14,070✔
464
            ComposeCSCache.Clear();
14,070✔
465
            ParseCache.Clear();
14,070✔
466
            StrippedTypeCache = null;
14,070✔
467

468
            // Seed with our primitive types
469
            for (int i = 0; i < PrimitiveTypes.Length; ++i)
478,380✔
470
            {
225,120✔
471
                ComposeDecCache[PrimitiveTypes[i].type] = PrimitiveTypes[i].str;
225,120✔
472
                ParseCache[PrimitiveTypes[i].str] = PrimitiveTypes[i].type;
225,120✔
473
            }
225,120✔
474
        }
14,070✔
475

476
        static UtilType()
477
        {
6✔
478
            // seed the cache
479
            ClearCache();
6✔
480
        }
6✔
481

482
        internal enum DecDatabaseStatus
483
        {
484
            Invalid,
485
            Abstract,
486
            Root,
487
            Branch,
488
        }
489
        private static Dictionary<Type, DecDatabaseStatus> GetDecDatabaseStatusCache = new Dictionary<Type, DecDatabaseStatus>();
6✔
490
        internal static DecDatabaseStatus GetDecDatabaseStatus(this Type type)
491
        {
36,267✔
492
            if (!GetDecDatabaseStatusCache.TryGetValue(type, out var result))
36,267✔
493
            {
549✔
494
                if (!typeof(Dec).IsAssignableFrom(type))
549✔
495
                {
3✔
496
                    Dbg.Err($"Queried the dec hierarchy status of a type {type} that doesn't even inherit from Dec.");
3✔
497

498
                    result = DecDatabaseStatus.Invalid;
3✔
499
                }
3✔
500
                else if (type.GetCustomAttribute<AbstractAttribute>(false) != null)
546✔
501
                {
24✔
502
                    if (!type.IsAbstract)
24✔
503
                    {
3✔
504
                        Dbg.Err($"Type {type} is tagged Dec.Abstract, but is not abstract.");
3✔
505
                    }
3✔
506

507
                    if (type.BaseType != typeof(object) && GetDecDatabaseStatus(type.BaseType) > DecDatabaseStatus.Abstract)
24✔
508
                    {
6✔
509
                        Dbg.Err($"Type {type} is tagged Dec.Abstract, but inherits from {type.BaseType} which is within the database.");
6✔
510
                    }
6✔
511

512
                    result = DecDatabaseStatus.Abstract;
24✔
513
                }
24✔
514
                else if (type.BaseType.GetCustomAttribute<AbstractAttribute>(false) != null)
522✔
515
                {
462✔
516
                    // 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.
517
                    GetDecDatabaseStatus(type.BaseType);
462✔
518

519
                    result = DecDatabaseStatus.Root;
462✔
520
                }
462✔
521
                else
522
                {
60✔
523
                    // 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.
524
                    GetDecDatabaseStatus(type.BaseType);
60✔
525

526
                    // Our parent isn't NotDatabaseRootAttribute. We are not a database root, but we also can't say anything meaningful about our parents.
527
                    result = DecDatabaseStatus.Branch;
60✔
528
                }
60✔
529

530
                GetDecDatabaseStatusCache.Add(type, result);
549✔
531
            }
549✔
532

533
            return result;
36,267✔
534
        }
36,267✔
535

536
        private static Dictionary<Type, Type> GetDecRootTypeCache = new Dictionary<Type, Type>();
6✔
537
        internal static Type GetDecRootType(this Type type)
538
        {
67,599✔
539
            if (!GetDecRootTypeCache.TryGetValue(type, out var result))
67,599✔
540
            {
540✔
541
                if (GetDecDatabaseStatus(type) <= DecDatabaseStatus.Abstract)
540✔
542
                {
18✔
543
                    Dbg.Err($"{type} does not exist within a database hierarchy.");
18✔
544
                    result = null;
18✔
545
                }
18✔
546
                else
547
                {
522✔
548
                    Type currentType = type;
522✔
549
                    while (GetDecDatabaseStatus(currentType) == DecDatabaseStatus.Branch)
591✔
550
                    {
69✔
551
                        currentType = currentType.BaseType;
69✔
552
                    }
69✔
553

554
                    result = currentType;
522✔
555
                }
522✔
556

557
                GetDecRootTypeCache.Add(type, result);
540✔
558
            }
540✔
559

560
            return result;
67,599✔
561
        }
67,599✔
562

563
        internal static bool CanBeShared(this Type type)
564
        {
1,116,879✔
565
            return Util.CanBeShared(type);
1,116,879✔
566
        }
1,116,879✔
567

568
        internal enum ParseModeCategory
569
        {
570
            Dec,
571
            Object,
572
            OrderedContainer,
573
            UnorderedContainer,
574
            Value,
575
        }
576
        internal static ParseModeCategory CalculateSerializationModeCategory(this Type type, Converter converter, bool isRootDec)
577
        {
976,581✔
578
            if (isRootDec && typeof(Dec).IsAssignableFrom(type))
976,581✔
579
            {
×
580
                return ParseModeCategory.Dec;
×
581
            }
582
            else if (false
976,581✔
583
                || type.IsPrimitive
976,581✔
584
                || type == typeof(string)
976,581✔
585
                || type == typeof(Type)
976,581✔
586
                || (!isRootDec && typeof(Dec).IsAssignableFrom(type)) // unnecessary isRootDec test here is to shortcut the expensive IsAssignableFrom call
976,581✔
587
                || typeof(Enum).IsAssignableFrom(type)
976,581✔
588
                || converter is ConverterString
976,581✔
589
                || (System.ComponentModel.TypeDescriptor.GetConverter(type)?.CanConvertFrom(typeof(string)) ?? false)   // this is last because it's slow
976,581✔
590
            )
976,581✔
591
            {
237,830✔
592
                return ParseModeCategory.Value;
237,830✔
593
            }
594
            else if (
738,751✔
595
                (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) ||
738,751✔
596
                type.IsArray
738,751✔
597
            )
738,751✔
598
            {
39,600✔
599
                return ParseModeCategory.OrderedContainer;
39,600✔
600
            }
601
            else if (
699,151✔
602
                (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) ||
699,151✔
603
                (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(HashSet<>))
699,151✔
604
            )
699,151✔
605
            {
21,333✔
606
                return ParseModeCategory.UnorderedContainer;
21,333✔
607
            }
608
            else if (type.IsGenericType && (
677,818✔
609
                    type.GetGenericTypeDefinition() == typeof(Tuple<>) ||
677,818✔
610
                    type.GetGenericTypeDefinition() == typeof(Tuple<,>) ||
677,818✔
611
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,>) ||
677,818✔
612
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,,>) ||
677,818✔
613
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,,,>) ||
677,818✔
614
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,,,,>) ||
677,818✔
615
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,,,,,>) ||
677,818✔
616
                    type.GetGenericTypeDefinition() == typeof(Tuple<,,,,,,,>) ||
677,818✔
617
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<>) ||
677,818✔
618
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,>) ||
677,818✔
619
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,>) ||
677,818✔
620
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,,>) ||
677,818✔
621
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,>) ||
677,818✔
622
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,>) ||
677,818✔
623
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,,>) ||
677,818✔
624
                    type.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,,,>)
677,818✔
625
                ))
677,818✔
626
            {
624✔
627
                return ParseModeCategory.Value;
624✔
628
            }
629
            else
630
            {
677,194✔
631
                return ParseModeCategory.Object;
677,194✔
632
            }
633
        }
976,581✔
634
    }
635
}
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