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

SamboyCoding / Cpp2IL / 29211911696

12 Jul 2026 10:38PM UTC coverage: 36.304% (+0.2%) from 36.132%
29211911696

push

github

web-flow
Diffable CS improvements (#577)

2734 of 8570 branches covered (31.9%)

Branch coverage included in aggregate %.

249 of 267 new or added lines in 6 files covered. (93.26%)

5078 of 12948 relevant lines covered (39.22%)

170756.73 hits per line

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

88.08
/Cpp2IL.Core/Utils/CsFileUtils.cs
1
using System;
2
using System.CodeDom.Compiler;
3
using System.Reflection;
4
using System.Text;
5
using Cpp2IL.Core.Logging;
6
using Cpp2IL.Core.Model.Contexts;
7
using LibCpp2IL;
8

9
namespace Cpp2IL.Core.Utils;
10

11
public static class CsFileUtils
12
{
13
    /// <summary>
14
    /// Returns the parameters of the given method as they would likely appear in a C# method signature.
15
    /// That is to say, joined with a comma and a space, and with each parameter expressed as its type, a space, then its name, and optionally a default value if one is set.
16
    /// Note this does not include the method name, the return type, or the parentheses around the parameters.
17
    /// </summary>
18
    /// <param name="method">The method to generate the parameter string for</param>
19
    /// <returns>A properly-formatted parameter string as described above.</returns>
20
    public static string GetMethodParameterString(MethodAnalysisContext method)
21
    {
22
        // ToString on the ParameterData will do the right thing.
23
        return string.Join(", ", method.Parameters);
37,227✔
24
    }
25

26
    /// <summary>
27
    /// Gets the C# access modifier string for the given type analysis context.
28
    /// Examples: "public", "internal", "private", "protected", "protected internal", "private protected".
29
    /// </summary>
30
    /// <param name="type">The type analysis context to inspect.</param>
31
    /// <returns>The access modifier keyword appropriate for the type.</returns>
32
    public static string GetAccessModifiers(TypeAnalysisContext type) => type.Visibility switch
7,409!
33
    {
7,409✔
34
        TypeAttributes.Public => "public",
3,655✔
35
        TypeAttributes.NotPublic => "internal",
2,017✔
36
        TypeAttributes.NestedPublic => "public",
669✔
37
        TypeAttributes.NestedAssembly => "internal",
276✔
38
        TypeAttributes.NestedPrivate => "private",
789✔
NEW
39
        TypeAttributes.NestedFamily => "protected",
×
40
        TypeAttributes.NestedFamORAssem => "protected internal",
3✔
NEW
41
        TypeAttributes.NestedFamANDAssem => "private protected",
×
NEW
42
        _ => throw new ArgumentOutOfRangeException($"Unknown visibility for type {type.FullName}: {type.Visibility}")
×
43
    };
7,409✔
44

45
    /// <summary>
46
    /// Gets the C# access modifier string for the given field analysis context.
47
    /// Examples: "public", "private", "protected", "internal", "protected internal", "private protected".
48
    /// </summary>
49
    /// <param name="field">The field analysis context to inspect.</param>
50
    /// <returns>The access modifier keyword appropriate for the field.</returns>
51
    public static string GetAccessModifiers(FieldAnalysisContext field) => field.Visibility switch
15,675!
52
    {
15,675✔
53
        FieldAttributes.Public => "public",
2,700✔
54
        FieldAttributes.Private => "private",
8,772✔
55
        FieldAttributes.Family => "protected",
174✔
56
        FieldAttributes.Assembly => "internal",
4,005✔
57
        FieldAttributes.FamORAssem => "protected internal",
24✔
NEW
58
        FieldAttributes.FamANDAssem => "private protected",
×
NEW
59
        _ => throw new ArgumentOutOfRangeException($"Unknown visibility for field {field.DeclaringType.FullName}.{field.Name}: {field.Visibility}")
×
60
    };
15,675✔
61

62
    /// <summary>
63
    /// Gets the C# access modifier string for the given method analysis context.
64
    /// </summary>
65
    /// <param name="method">The method analysis context to inspect.</param>
66
    /// <returns>The access modifier keyword appropriate for the method.</returns>
67
    public static string GetAccessModifiers(MethodAnalysisContext method) => GetAccessModifiers(method.Visibility);
37,254✔
68

69
    /// <summary>
70
    /// Gets the C# access modifier string for the given property analysis context.
71
    /// </summary>
72
    /// <param name="property">The property analysis context to inspect.</param>
73
    /// <returns>The access modifier keyword appropriate for the property.</returns>
74
    public static string GetAccessModifiers(PropertyAnalysisContext property) => GetAccessModifiers(property.Visibility);
5,730✔
75

76
    /// <summary>
77
    /// Gets the C# access modifier string for the given event analysis context.
78
    /// </summary>
79
    /// <param name="evt">The event analysis context to inspect.</param>
80
    /// <returns>The access modifier keyword appropriate for the event.</returns>
81
    public static string GetAccessModifiers(EventAnalysisContext evt) => GetAccessModifiers(evt.Visibility);
15✔
82

83
    private static string GetAccessModifiers(MethodAttributes visibility) => visibility switch
42,999!
84
    {
42,999✔
85
        MethodAttributes.Public => "public",
25,029✔
86
        MethodAttributes.Private or MethodAttributes.PrivateScope => "private",
9,030✔
87
        MethodAttributes.Family => "protected",
1,401✔
88
        MethodAttributes.Assembly => "internal",
7,464✔
89
        MethodAttributes.FamORAssem => "protected internal",
75✔
NEW
90
        MethodAttributes.FamANDAssem => "private protected",
×
NEW
91
        _ => throw new ArgumentOutOfRangeException($"Unknown visibility: {visibility}")
×
92
    };
42,999✔
93

94
    /// <summary>
95
    /// Returns the C# keyword that declares the kind of type represented by the context.
96
    /// Examples: "class", "struct", "enum", "interface", "delegate".
97
    /// </summary>
98
    /// <param name="type">The type analysis context to evaluate.</param>
99
    /// <returns>The declaration keyword for the type.</returns>
100
    public static string GetTypeDeclarationKeyword(TypeAnalysisContext type)
101
    {
102
        if (type.IsEnumType)
7,409✔
103
            return "enum";
849✔
104
        if (type.IsValueType)
6,560✔
105
            return "struct";
1,558✔
106
        if (type.IsInterface)
5,002✔
107
            return "interface";
364✔
108
        if (type.IsDelegate)
4,638✔
109
            return "delegate";
333✔
110
        return "class";
4,305✔
111
    }
112

113
    /// <summary>
114
    /// Returns a class-level inheritance modifier for the given type, if applicable.
115
    /// Examples: "static", "abstract", "sealed", or null when no modifier should be emitted.
116
    /// </summary>
117
    /// <param name="type">The type analysis context to inspect.</param>
118
    /// <returns>The inheritance modifier keyword or null.</returns>
119
    public static string? GetClassInheritanceKeyword(TypeAnalysisContext type)
120
    {
121
        if (type.IsStatic)
7,409✔
122
            return "static";
454✔
123
        if (type.IsAbstract && !type.IsInterface)
6,955✔
124
            return "abstract";
268✔
125
        if (type.IsSealed && !type.IsValueType && !type.IsDelegate)
6,687✔
126
            return "sealed";
1,506✔
127
        return null;
5,181✔
128
    }
129

130
    /// <summary>
131
    /// Returns all the keywords that would be present in the c# source file to generate this type, i.e. access modifiers, static/sealed/etc, and the type of type (class, enum, interface).
132
    /// Does not include the name of the type.
133
    /// </summary>
134
    /// <param name="type">The type to generate the keywords for</param>
135
    public static string GetKeyWordsForType(TypeAnalysisContext type)
136
    {
137
        var visibility = GetAccessModifiers(type);
7,409✔
138
        var inheritance = GetClassInheritanceKeyword(type);
7,409✔
139
        var declaration = GetTypeDeclarationKeyword(type);
7,409✔
140
        return inheritance is null ? $"{visibility} {declaration}" : $"{visibility} {inheritance} {declaration}";
7,409✔
141
    }
142

143
    /// <summary>
144
    /// Returns all the keywords that would be present in the c# source file to generate this field, i.e. access modifiers, static/const/etc.
145
    /// Does not include the type of the field or its name.
146
    /// </summary>
147
    /// <param name="field">The field to generate keywords for</param>
148
    public static string GetKeyWordsForField(FieldAnalysisContext field)
149
    {
150
        var sb = new StringBuilder();
15,675✔
151
        var attributes = field.Attributes;
15,675✔
152

153
        sb.Append(GetAccessModifiers(field)).Append(' ');
15,675✔
154

155
        if (attributes.HasFlag(FieldAttributes.Literal))
15,675✔
156
            sb.Append("const ");
549✔
157
        else
158
        {
159
            if (attributes.HasFlag(FieldAttributes.Static))
15,126✔
160
                sb.Append("static ");
3,315✔
161

162
            if (attributes.HasFlag(FieldAttributes.InitOnly))
15,126✔
163
                sb.Append("readonly ");
3,057✔
164
        }
165

166
        return sb.ToString().TrimEnd();
15,675✔
167
    }
168

169
    private static string? GetVirtualLookupKeyword(bool isInterfaceMember, bool isStatic, bool isAbstract, bool isVirtual, bool isNewSlot, bool isFinal)
170
    {
171
        // slot-related modifiers like abstract, virtual, override, sealed
172

173
        if (isInterfaceMember)
42,972✔
174
        {
175
            if (isAbstract)
903✔
176
                return isStatic ? "abstract" : null;
900!
177
            else if (isVirtual)
3!
178
                return isStatic ? "virtual" : null;
3!
179
            else
NEW
180
                return isStatic ? null : "sealed";
×
181
        }
182
        else if (isAbstract)
42,069✔
183
        {
184
            return "abstract";
675✔
185
        }
186
        else if (isVirtual)
41,394✔
187
        {
188
            if (isNewSlot)
13,032✔
189
                return isFinal ? null : "virtual"; // final, virtual, newslot means an interface implementation
7,227✔
190
            else
191
                return isFinal ? "sealed override" : "override";
5,805✔
192
        }
193
        else
194
        {
195
            return null;
28,362✔
196
        }
197
    }
198

199
    /// <summary>
200
    /// Determines the appropriate virtual/slot-related keyword for a method.
201
    /// Examples: "abstract", "virtual", "override", "sealed override", or null when no slot keyword applies.
202
    /// </summary>
203
    /// <param name="method">The method analysis context to inspect.</param>
204
    /// <returns>The slot-related keyword or null if none should be emitted.</returns>
205
    public static string? GetVirtualLookupKeyword(MethodAnalysisContext method)
206
    {
207
        return GetVirtualLookupKeyword(method.DeclaringType?.IsInterface ?? false, method.IsStatic, method.IsAbstract, method.IsVirtual, method.IsNewSlot, method.IsFinal);
37,227!
208
    }
209

210
    /// <summary>
211
    /// Determines the appropriate virtual/slot-related keyword for a property.
212
    /// </summary>
213
    /// <param name="property">The property analysis context to inspect.</param>
214
    /// <returns>The slot-related keyword or null if none should be emitted.</returns>
215
    public static string? GetVirtualLookupKeyword(PropertyAnalysisContext property)
216
    {
217
        return GetVirtualLookupKeyword(property.DeclaringType.IsInterface, property.IsStatic, property.IsAbstract, property.IsVirtual, property.IsNewSlot, property.IsFinal);
5,730✔
218
    }
219

220
    /// <summary>
221
    /// Determines the appropriate virtual/slot-related keyword for an event.
222
    /// </summary>
223
    /// <param name="evt">The event analysis context to inspect.</param>
224
    /// <returns>The slot-related keyword or null if none should be emitted.</returns>
225
    public static string? GetVirtualLookupKeyword(EventAnalysisContext evt)
226
    {
227
        return GetVirtualLookupKeyword(evt.DeclaringType.IsInterface, evt.IsStatic, evt.IsAbstract, evt.IsVirtual, evt.IsNewSlot, evt.IsFinal);
15✔
228
    }
229

230
    /// <summary>
231
    /// Returns all the keywords that would be present in the c# source file to generate this method, i.e. access modifiers, static/abstract/etc.
232
    /// Does not include the return type, name, or parameters.
233
    /// </summary>
234
    /// <param name="method">The method to generate keywords for</param>
235
    /// <param name="parentVisibility">The visibility of the parent, used to determine if the method's visibility should be included</param>
236
    public static string GetKeyWordsForMethod(MethodAnalysisContext method, MethodAttributes? parentVisibility = null)
237
    {
238
        var sb = new StringBuilder();
43,491✔
239

240
        if (method.Visibility != parentVisibility)
43,491✔
241
            sb.Append(GetAccessModifiers(method)).Append(' ');
37,254✔
242

243
        if (parentVisibility is null)
43,491✔
244
        {
245
            if (method.IsStatic)
37,227✔
246
                sb.Append("static ");
10,746✔
247

248
            var slotKeyword = GetVirtualLookupKeyword(method);
37,227✔
249
            if (slotKeyword != null)
37,227✔
250
                sb.Append(slotKeyword);
7,380✔
251
        }
252

253
        return sb.ToString().TrimEnd();
43,491✔
254
    }
255

256
    /// <summary>
257
    /// Returns all the keywords that would be present in the c# source file to generate this event, i.e. access modifiers, static/abstract/etc.
258
    /// Does not include the event type or name
259
    /// </summary>
260
    /// <param name="evt">The event to generate keywords for</param>
261
    public static string GetKeyWordsForEvent(EventAnalysisContext evt)
262
    {
263
        var sb = new StringBuilder();
15✔
264

265
        sb.Append(GetAccessModifiers(evt)).Append(' ');
15✔
266

267
        if (evt.IsStatic)
15✔
268
            sb.Append("static ");
3✔
269

270
        var slotKeyword = GetVirtualLookupKeyword(evt);
15✔
271
        if (slotKeyword != null)
15!
NEW
272
            sb.Append(slotKeyword).Append(' ');
×
273

274
        sb.Append("event");
15✔
275

276
        return sb.ToString();
15✔
277
    }
278

279
    /// <summary>
280
    /// Returns all the keywords that would be present in the c# source file to generate this property, i.e. access modifiers, static/abstract/etc.
281
    /// Does not include the property type or name
282
    /// </summary>
283
    /// <param name="prop">The property to generate keywords for</param>
284
    public static string GetKeyWordsForProperty(PropertyAnalysisContext prop)
285
    {
286
        var sb = new StringBuilder();
5,730✔
287

288
        sb.Append(GetAccessModifiers(prop)).Append(' ');
5,730✔
289

290
        if (prop.IsStatic)
5,730✔
291
            sb.Append("static ");
549✔
292

293
        var slotKeyword = GetVirtualLookupKeyword(prop);
5,730✔
294
        if (slotKeyword != null)
5,730✔
295
            sb.Append(slotKeyword);
1,686✔
296

297
        return sb.ToString().TrimEnd();
5,730✔
298
    }
299

300
    /// <summary>
301
    /// Writes all the custom attributes for the given entity to the given writer, as they would appear in a C# source file (i.e. properly wrapped in square brackets, with params if known).
302
    /// Each attribute is written on its own line, indented according to the writer's current <see cref="IndentedTextWriter.Indent"/> level.
303
    /// </summary>
304
    /// <param name="context">The entity to write custom attribute strings for</param>
305
    /// <param name="writer">The writer to write the custom attribute strings to</param>
306
    /// <param name="analyze">True to call <see cref="HasCustomAttributes.AnalyzeCustomAttributeData"/> before generating.</param>
307
    /// <param name="includeIncomplete">True to emit custom attributes even if they have required parameters that aren't known</param>
308
    public static void WriteCustomAttributeStrings(HasCustomAttributes context, IndentedTextWriter writer, bool analyze = true, bool includeIncomplete = true)
309
    {
310
        if (analyze)
72,315!
311
            context.AnalyzeCustomAttributeData();
72,315✔
312

313
        //Sort alphabetically by type name
314
        context.CustomAttributes!.SortByExtractedKey(a => a.Constructor.DeclaringType!.Name);
80,997✔
315

316
        foreach (var analyzedCustomAttribute in context.CustomAttributes!)
169,530✔
317
        {
318
            if (!includeIncomplete && !analyzedCustomAttribute.IsSuitableForEmission)
12,450!
319
                continue;
320

321
            try
322
            {
323
                writer.WriteLine(analyzedCustomAttribute.ToString());
12,450✔
324
            }
12,450✔
325
            catch (Exception e)
×
326
            {
327
                Logger.WarnNewline("Exception printing/formatting custom attribute: " + e, "C# Generator");
×
NEW
328
                writer.WriteLine($"/*Cpp2IL: Exception outputting custom attribute of type {analyzedCustomAttribute.Constructor.DeclaringType?.Name ?? "<unknown type?>"}*/");
×
329
            }
×
330
        }
331
    }
72,315✔
332

333
    /// <summary>
334
    /// Returns the C#-style name for the given type analysis context.
335
    /// Handles built-in System type aliases (e.g. System.Int32 -> int), arrays, pointers, by-ref and generic instances.
336
    /// </summary>
337
    /// <param name="type">The type analysis context to convert to a C# type name.</param>
338
    /// <returns>The C# type name as it should appear in source.</returns>
339
    public static string GetTypeName(TypeAnalysisContext type)
340
    {
341
        if (type is WrappedTypeAnalysisContext wrapped)
129,921✔
342
        {
343
            var elementTypeName = GetTypeName(wrapped.ElementType);
9,621!
344
            switch (wrapped)
345
            {
346
                case ArrayTypeAnalysisContext arrayType:
347
                    {
348
                        return arrayType.Rank switch
×
349
                        {
×
350
                            1 => elementTypeName + "[]",
×
351
                            2 => elementTypeName + "[,]",
×
352
                            3 => elementTypeName + "[,,]",
×
353
                            _ => elementTypeName + "[" + new string(',', arrayType.Rank - 1) + "]"
×
354
                        };
×
355
                    }
356
                case SzArrayTypeAnalysisContext:
357
                    return elementTypeName + "[]";
5,343✔
358
                case PointerTypeAnalysisContext:
359
                    return elementTypeName + "*";
1,383✔
360
                case ByRefTypeAnalysisContext:
361
                    return elementTypeName; //Remove trailing & for ref params
2,895✔
362
                default:
363
                    return elementTypeName;
×
364
            }
365
        }
366

367
        if (type is GenericInstanceTypeAnalysisContext genericInstanceType)
120,300✔
368
        {
369
            var genericTypeName = GetTypeName(genericInstanceType.GenericType);
3,993✔
370
            var backTickIndex = genericTypeName.LastIndexOf('`');
3,993✔
371
            return backTickIndex > 0 ? genericTypeName[..backTickIndex] : genericTypeName;
3,993✔
372
        }
373

374
        if (type.Namespace is "System")
116,307✔
375
        {
376
            return type.Name switch
77,322✔
377
            {
77,322✔
378
                "Void" => "void",
8,475✔
379
                "Boolean" => "bool",
10,305✔
380
                "Byte" => "byte",
2,100✔
381
                "SByte" => "sbyte",
291✔
382
                "Char" => "char",
1,932✔
383
                "Decimal" => "decimal",
330✔
384
                "Single" => "float",
1,464✔
385
                "Double" => "double",
438✔
386
                "Int32" => "int",
16,191✔
387
                "UInt32" => "uint",
891✔
388
                "Int64" => "long",
1,206✔
389
                "UInt64" => "ulong",
540✔
390
                "Int16" => "short",
339✔
391
                "UInt16" => "ushort",
444✔
392
                "IntPtr" => "nint",
2,766✔
393
                "UIntPtr" => "nuint",
57✔
394
                "String" => "string",
10,011✔
395
                "Object" => "object",
5,700✔
396
                _ => type.Name,
13,842✔
397
            };
77,322✔
398
        }
399
        else
400
        {
401
            return type.Name;
38,985✔
402
        }
403
    }
404

405
    /// <summary>
406
    /// Writes inheritance data (base class and interfaces) for the given type to the given writer.
407
    /// If the base class is System.Object, System.ValueType, System.Enum, or System.MulticastDelegate, it will be ignored
408
    /// </summary>
409
    /// <param name="type">The type analysis context whose inheritance is to be written.</param>
410
    /// <param name="writer">The writer to which the inheritance information will be written.</param>
411
    public static void WriteInheritanceInfo(TypeAnalysisContext type, IndentedTextWriter writer)
412
    {
413
        var baseType = type.BaseType;
7,404✔
414
        var needsBaseClass = baseType is not ReferencedTypeAnalysisContext and ({ Namespace: not "System" } or { Name: not "Object" and not "ValueType" and not "Enum" and not "MulticastDelegate" });
7,404✔
415
        if (needsBaseClass)
7,404✔
416
        {
417
            writer.Write(" : ");
1,746✔
418
            writer.Write(GetTypeName(baseType!));
1,746✔
419
        }
420

421
        //Interfaces
422
        if (type.InterfaceContexts.Count <= 0)
7,404✔
423
            return;
6,315✔
424

425
        if (!needsBaseClass)
1,089✔
426
            writer.Write(" : ");
975✔
427

428
        var addComma = needsBaseClass;
1,089✔
429
        foreach (var iface in type.InterfaceContexts)
6,540✔
430
        {
431
            if (addComma)
2,181✔
432
                writer.Write(", ");
1,206✔
433

434
            addComma = true;
2,181✔
435

436
            writer.Write(GetTypeName(iface));
2,181✔
437
        }
438
    }
1,089✔
439
}
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