-
Notifications
You must be signed in to change notification settings - Fork 29
/
CodeTypeReferenceBuilder.cs
156 lines (131 loc) · 6.72 KB
/
CodeTypeReferenceBuilder.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
using Mono.Cecil;
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Linq;
namespace PublicApiGenerator
{
internal static class CodeTypeReferenceBuilder
{
private const int MAX_COUNT = 100;
internal static CodeTypeReference CreateCodeTypeReference(this TypeReference type, ICustomAttributeProvider attributeProvider = null, NullableMode mode = NullableMode.Default)
{
return CreateCodeTypeReferenceWithNullabilityMap(type, attributeProvider.GetNullabilityMap().GetEnumerator(), mode, false);
}
static CodeTypeReference CreateCodeTypeReferenceWithNullabilityMap(TypeReference type, IEnumerator<bool?> nullabilityMap, NullableMode mode, bool disableNested)
{
var typeName = GetTypeName(type, nullabilityMap, mode, disableNested);
if (type.IsValueType && type.Name == "Nullable`1" && type.Namespace == "System")
{
// unwrap System.Nullable<Type> into Type? for readability
var genericArgs = type is IGenericInstance instance ? instance.GenericArguments : type.HasGenericParameters ? type.GenericParameters.Cast<TypeReference>() : null;
return CreateCodeTypeReferenceWithNullabilityMap(genericArgs.Single(), nullabilityMap, NullableMode.Force, disableNested);
}
else
{
return new CodeTypeReference(typeName, CreateGenericArguments(type, nullabilityMap));
}
}
static CodeTypeReference[] CreateGenericArguments(TypeReference type, IEnumerator<bool?> nullabilityMap)
{
// ReSharper disable once RedundantEnumerableCastCall
var genericArgs = type is IGenericInstance instance ? instance.GenericArguments : type.HasGenericParameters ? type.GenericParameters.Cast<TypeReference>() : null;
if (genericArgs == null) return null;
var genericArguments = new List<CodeTypeReference>();
foreach (var argument in genericArgs)
{
genericArguments.Add(CreateCodeTypeReferenceWithNullabilityMap(argument, nullabilityMap, NullableMode.Default, false));
}
return genericArguments.ToArray();
}
internal static IEnumerable<bool?> GetNullabilityMap(this ICustomAttributeProvider attributeProvider)
{
var nullableAttr = attributeProvider?.CustomAttributes.SingleOrDefault(d => d.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute");
if (nullableAttr == null)
{
foreach (var provider in NullableContext.Providers)
{
nullableAttr = provider.CustomAttributes.SingleOrDefault(d => d.AttributeType.FullName == "System.Runtime.CompilerServices.NullableContextAttribute");
if (nullableAttr != null)
break;
}
}
if (nullableAttr == null)
return Enumerable.Repeat((bool?)null, MAX_COUNT);
var value = nullableAttr.ConstructorArguments[0].Value;
if (value is CustomAttributeArgument[] arguments)
return arguments.Select(a => Convert((byte)a.Value));
return Enumerable.Repeat(Convert((byte)value), MAX_COUNT);
// https://github.com/dotnet/roslyn/blob/master/docs/features/nullable-metadata.md
// returns:
// true : explicitly nullable
// false: explicitly not nullable
// null : oblivious
bool? Convert(byte value)
{
switch (value)
{
case 2: return true;
case 1: return false;
case 0: return null;
default: throw new NotSupportedException(value.ToString());
}
}
}
// The compiler optimizes the size of metadata bypassing a sequence of bytes for value types.
// Thus, it can even delete the entire NullableAttribute if the whole signature consists only of value types,
// for example KeyValuePair<int, int?>, thus we can call IsNullable() only by looking first deep into the signature
private static bool HasAnyReferenceType(TypeReference type)
{
if (!type.IsValueType)
return true;
// ReSharper disable once RedundantEnumerableCastCall
var genericArgs = type is IGenericInstance instance ? instance.GenericArguments : type.HasGenericParameters ? type.GenericParameters.Cast<TypeReference>() : null;
if (genericArgs == null) return false;
foreach (var argument in genericArgs)
{
if (HasAnyReferenceType(argument))
return true;
}
return false;
}
static string GetTypeName(TypeReference type, IEnumerator<bool?> nullabilityMap, NullableMode mode, bool disableNested)
{
bool nullable = mode != NullableMode.Disable && (mode == NullableMode.Force || HasAnyReferenceType(type) && IsNullable());
var typeName = GetTypeNameCore(type, nullabilityMap, nullable, disableNested);
if (nullable && typeName != "System.Void")
typeName = CSharpAlias.Get(typeName) + "?";
return typeName;
bool IsNullable()
{
if (nullabilityMap == null)
return false;
if (!nullabilityMap.MoveNext())
{
throw new InvalidOperationException("Not enough nullability information");
}
return nullabilityMap.Current == true;
}
}
static string GetTypeNameCore(TypeReference type, IEnumerator<bool?> nullabilityMap, bool nullable, bool disableNested)
{
if (type.IsGenericParameter)
{
return type.Name;
}
if (type is ArrayType array)
{
if (nullable)
return CSharpAlias.Get(GetTypeName(array.ElementType, nullabilityMap, NullableMode.Default, disableNested)) + "[]";
else
return GetTypeName(array.ElementType, nullabilityMap, NullableMode.Default, disableNested) + "[]";
}
if (!type.IsNested || disableNested)
{
var name = type is RequiredModifierType modType ? modType.ElementType.Name : type.Name;
return (!string.IsNullOrEmpty(type.Namespace) ? (type.Namespace + ".") : "") + name;
}
return GetTypeName(type.DeclaringType, null, NullableMode.Default, false) + "." + GetTypeName(type, null, NullableMode.Default, true);
}
}
}