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

zorbathut / dec / 16127435272

07 Jul 2025 08:42PM UTC coverage: 90.123% (-0.05%) from 90.17%
16127435272

push

github

zorbathut
Official support for nullable value types.

5146 of 5710 relevant lines covered (90.12%)

220034.89 hits per line

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

98.04
/src/UtilReflection.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Linq;
4
using System.Reflection;
5

6
namespace Dec
7
{
8
    internal static class UtilReflection
9
    {
10
        internal static FieldInfo GetFieldFromHierarchy(this Type type, string name)
11
        {
380,502✔
12
            FieldInfo result = null;
380,502✔
13
            Type resultType = null;
380,502✔
14

15
            Type curType = type;
380,502✔
16
            while (curType != null)
1,313,250✔
17
            {
932,748✔
18
                FieldInfo typeField = curType.GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
932,748✔
19
                if (typeField != null)
932,748✔
20
                {
380,430✔
21
                    if (result == null)
380,430✔
22
                    {
380,358✔
23
                        result = typeField;
380,358✔
24
                        resultType = curType;
380,358✔
25
                    }
380,358✔
26
                    else
27
                    {
72✔
28
                        Dbg.Err($"Found multiple examples of field named `{name}` in type hierarchy {type}; found in {resultType} and {curType}");
72✔
29
                    }
72✔
30
                }
380,430✔
31

32
                curType = curType.BaseType;
932,748✔
33
            }
932,748✔
34
            return result;
380,502✔
35
        }
380,502✔
36

37
        internal static IEnumerable<FieldInfo> GetSerializableFieldsFromHierarchy(this Type type)
38
        {
66,788✔
39
            // this probably needs to be cached
40

41
            var seenFields = new HashSet<string>();
66,788✔
42

43
            Type curType = type;
66,788✔
44
            while (curType != null)
235,226✔
45
            {
168,510✔
46
                foreach (var field in curType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly))
1,273,426✔
47
                {
383,984✔
48
                    if (field.IsBackingField())
383,984✔
49
                    {
26,478✔
50
                        continue;
26,478✔
51
                    }
52

53
                    if (field.GetCustomAttribute<IndexAttribute>() != null)
357,506✔
54
                    {
666✔
55
                        // we don't save indices
56
                        continue;
666✔
57
                    }
58

59
                    if (field.GetCustomAttribute<NonSerializedAttribute>() != null)
356,840✔
60
                    {
290✔
61
                        // we also don't save nonserialized
62
                        continue;
290✔
63
                    }
64

65
                    if (seenFields.Contains(field.Name))
356,550✔
66
                    {
42✔
67
                        Dbg.Err($"Found duplicates of field `{field}`; base fields will be ignored");
42✔
68
                        continue;
42✔
69
                    }
70

71
                    yield return field;
356,508✔
72
                    seenFields.Add(field.Name);
356,436✔
73
                }
356,436✔
74

75
                curType = curType.BaseType;
168,438✔
76
            }
168,438✔
77
        }
66,716✔
78

79
        internal static bool IsUserAssembly(this Assembly asm)
80
        {
1,432✔
81
            var name = asm.FullName;
1,432✔
82

83
            // Filter out system libraries
84
            if (name.StartsWith("mscorlib,") || name.StartsWith("System,") || name.StartsWith("System.") || name.StartsWith("netstandard"))
1,432✔
85
            {
658✔
86
                return false;
658✔
87
            }
88

89
            // Filter out Mono
90
            if (name.StartsWith("Mono."))
774✔
91
            {
18✔
92
                return false;
18✔
93
            }
94

95
            // Filter out nunit, almost entirely so our test results look better
96
            if (name.StartsWith("nunit.framework,"))
756✔
97
            {
18✔
98
                return false;
18✔
99
            }
100

101
            // Filter out Unity
102
            if (name.StartsWith("Unity.") || name.StartsWith("UnityEngine,") || name.StartsWith("UnityEngine.") || name.StartsWith("UnityEditor,") || name.StartsWith("UnityEditor.") || name.StartsWith("ExCSS.Unity,"))
738✔
103
            {
480✔
104
                return false;
480✔
105
            }
106

107
            // Filter out dec
108
            if (name.StartsWith("dec,"))
258✔
109
            {
18✔
110
                return false;
18✔
111
            }
112

113
            return true;
240✔
114
        }
1,432✔
115

116
        internal static IEnumerable<Assembly> GetAllUserAssemblies()
117
        {
12✔
118
            return AppDomain.CurrentDomain.GetAssemblies().Where(asm => asm.IsUserAssembly());
862✔
119
        }
12✔
120

121
        internal static IEnumerable<Type> GetAllUserTypes()
122
        {
12✔
123
            return GetAllUserAssemblies().SelectMany(a => a.GetTypes()).Where(t => {
16,908✔
124
                var ns = t.Namespace;
16,686✔
125
                if (ns == null)
16,686✔
126
                {
378✔
127
                    return true;
378✔
128
                }
12✔
129

12✔
130
                return !t.Namespace.StartsWith("Dec.") && t.Namespace != "Dec";
16,320✔
131
            });
16,686✔
132
        }
12✔
133

134
        internal struct IndexInfo
135
        {
136
            public Type type;
137
            public FieldInfo field;
138
        }
139
        internal static System.Collections.Concurrent.ConcurrentDictionary<Type, IndexInfo[]> IndexInfoCached = new System.Collections.Concurrent.ConcurrentDictionary<Type, IndexInfo[]>();
12✔
140
        internal static IndexInfo[] GetIndicesForType(Type type)
141
        {
141,456✔
142
            if (IndexInfoCached.TryGetValue(type, out var result))
141,456✔
143
            {
92,210✔
144
                // found it in cache, we're done
145
                return result;
92,210✔
146
            }
147

148
            IndexInfo[] indices = null;
49,246✔
149

150
            if (type.BaseType != null)
49,246✔
151
            {
35,212✔
152
                indices = GetIndicesForType(type.BaseType);
35,212✔
153
            }
35,212✔
154

155
            FieldInfo matchedField = null;
49,246✔
156
            foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
284,662✔
157
            {
68,462✔
158
                if (field.GetCustomAttribute<IndexAttribute>() != null)
68,462✔
159
                {
324✔
160
                    if (matchedField != null)
324✔
161
                    {
36✔
162
                        Dbg.Err($"Too many indices in type {type} (found `{matchedField}` and `{field}`); only one will be filled");
36✔
163
                    }
36✔
164

165
                    matchedField = field;
324✔
166
                }
324✔
167
            }
68,462✔
168

169
            if (matchedField != null)
49,246✔
170
            {
288✔
171
                IndexInfo[] indicesWorking;
172

173
                if (indices != null)
288✔
174
                {
36✔
175
                    indicesWorking = new IndexInfo[indices.Length + 1];
36✔
176
                    Array.Copy(indices, indicesWorking, indices.Length);
36✔
177
                }
36✔
178
                else
179
                {
252✔
180
                    indicesWorking = new IndexInfo[1];
252✔
181
                }
252✔
182

183
                indicesWorking[indicesWorking.Length - 1] = new IndexInfo { type = type, field = matchedField };
288✔
184

185
                indices = indicesWorking;
288✔
186
            }
288✔
187

188
            IndexInfoCached[type] = indices;
49,246✔
189

190
            return indices;
49,246✔
191
        }
141,456✔
192

193
        internal static bool IsBackingField(this FieldInfo field)
194
        {
383,984✔
195
            // I wish I could find something more authoritative on this.
196
            return field.Name.StartsWith("<");
383,984✔
197
        }
383,984✔
198

199
        internal static object CreateInstanceSafe(this Type type, string errorType, ReaderNode node)
200
        {
698,796✔
201
            string BuiltContext()
202
            {
144✔
203
                return node?.GetContext().ToString() ?? "setup";
144✔
204
            }
144✔
205

206
            if (type.IsAbstract)
698,796✔
207
            {
24✔
208
                Dbg.Err($"{BuiltContext()}: Attempting to create {errorType} of abstract type {type}");
24✔
209
                return null;    // thankfully all abstract types can accept being null
24✔
210
            }
211
            else if (type.IsArray)
698,772✔
212
            {
624✔
213
                // Special handling, we need a fancy constructor with an int array parameter
214
                // Conveniently, arrays are really easy to deal with in this pathway :D
215
                return Array.CreateInstance(type.GetElementType(), node.GetArrayDimensions(type.GetArrayRank()));
624✔
216
            }
217
            else if (!type.IsValueType && type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { }, null) == null)
698,148✔
218
            {
120✔
219
                // Note: Structs don't have constructors. I actually can't tell if ints do, I'm kind of bypassing that system.
220

221
                Dbg.Err($"{BuiltContext()}: Attempting to create {errorType} of type {type} without a no-argument constructor");
120✔
222
                return null;    // similarly, anything that is capable of not having a no-argument constructor can accept being null
120✔
223
            }
224
            else if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
698,028✔
225
            {
48✔
226
                // we actually just treat this like the object itself, C# will handle the details
227
                return CreateInstanceSafe(type.GenericTypeArguments[0], errorType, node);
48✔
228
            }
229
            else
230
            {
697,980✔
231
                try
232
                {
697,980✔
233
                    var result = Activator.CreateInstance(type, true);
697,980✔
234
                    if (result == null)
697,932✔
235
                    {
×
236
                        // This is difficult to test; there are very few things that can get CreateInstance to return null, and we supposedly handle all of them in the above tests.
237
                        // In theory a malformed COM object might do it.
238
                        Dbg.Err($"{BuiltContext()}: {errorType} of type {type} was not properly created; this will cause issues");
×
239
                    }
×
240
                    return result;
697,932✔
241
                }
242
                catch (TargetInvocationException e)
48✔
243
                {
48✔
244
                    Dbg.Ex(e);
48✔
245
                    return null;
48✔
246
                }
247
            }
248
        }
698,796✔
249
    }
250
}
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