|
3 | 3 | namespace ExpressiveSharp.Generator.Emitter; |
4 | 4 |
|
5 | 5 | /// <summary> |
6 | | -/// Tracks and deduplicates <c>private static readonly</c> reflection field declarations |
7 | | -/// (<see cref="System.Reflection.MethodInfo"/>, <see cref="System.Reflection.PropertyInfo"/>, |
8 | | -/// <see cref="System.Reflection.ConstructorInfo"/>, <see cref="System.Reflection.FieldInfo"/>) |
| 6 | +/// Returns inline reflection expressions for |
| 7 | +/// <see cref="System.Reflection.MethodInfo"/>, <see cref="System.Reflection.PropertyInfo"/>, |
| 8 | +/// <see cref="System.Reflection.ConstructorInfo"/>, and <see cref="System.Reflection.FieldInfo"/> |
9 | 9 | /// needed by the emitted expression-tree-building code. |
| 10 | +/// Each <c>Ensure*</c> method returns a C# expression string that evaluates to the |
| 11 | +/// reflection object at runtime, rather than a static field name. |
10 | 12 | /// </summary> |
11 | 13 | internal sealed class ReflectionFieldCache |
12 | 14 | { |
13 | 15 | private static readonly SymbolDisplayFormat _fullyQualifiedFormat = |
14 | 16 | SymbolDisplayFormat.FullyQualifiedFormat; |
15 | 17 |
|
16 | | - private readonly string _prefix; |
17 | | - private readonly Dictionary<string, string> _fieldNamesByKey = new(); |
18 | | - private readonly List<string> _declarations = new(); |
19 | | - private int _propertyCounter; |
20 | | - private int _methodCounter; |
21 | | - private int _constructorCounter; |
22 | | - private int _fieldCounter; |
| 18 | + private readonly Dictionary<string, string> _expressionsByKey = new(); |
23 | 19 |
|
24 | 20 | public ReflectionFieldCache(string prefix = "") |
25 | 21 | { |
26 | | - _prefix = prefix; |
27 | 22 | } |
28 | 23 |
|
29 | 24 | /// <summary> |
30 | | - /// Returns the field name for a cached <see cref="System.Reflection.PropertyInfo"/>, |
31 | | - /// creating the declaration if this property hasn't been seen before. |
| 25 | + /// Returns an inline reflection expression for a <see cref="System.Reflection.PropertyInfo"/>. |
32 | 26 | /// </summary> |
33 | 27 | public string EnsurePropertyInfo(IPropertySymbol property) |
34 | 28 | { |
35 | 29 | var typeFqn = property.ContainingType.ToDisplayString(_fullyQualifiedFormat); |
36 | 30 | var key = $"P:{typeFqn}.{property.Name}"; |
37 | | - if (_fieldNamesByKey.TryGetValue(key, out var fieldName)) |
38 | | - return fieldName; |
| 31 | + if (_expressionsByKey.TryGetValue(key, out var cached)) |
| 32 | + return cached; |
39 | 33 |
|
40 | | - fieldName = $"_{_prefix}p{_propertyCounter++}"; |
41 | | - var declaration = $"""private static readonly global::System.Reflection.PropertyInfo {fieldName} = typeof({typeFqn}).GetProperty("{property.Name}");"""; |
42 | | - _fieldNamesByKey[key] = fieldName; |
43 | | - _declarations.Add(declaration); |
44 | | - return fieldName; |
| 34 | + var expr = $"typeof({typeFqn}).GetProperty(\"{property.Name}\")"; |
| 35 | + _expressionsByKey[key] = expr; |
| 36 | + return expr; |
45 | 37 | } |
46 | 38 |
|
47 | 39 | /// <summary> |
48 | | - /// Returns the field name for a cached <see cref="System.Reflection.FieldInfo"/>, |
49 | | - /// creating the declaration if this field hasn't been seen before. |
| 40 | + /// Returns an inline reflection expression for a <see cref="System.Reflection.FieldInfo"/>. |
50 | 41 | /// </summary> |
51 | 42 | public string EnsureFieldInfo(IFieldSymbol field) |
52 | 43 | { |
53 | 44 | var typeFqn = field.ContainingType.ToDisplayString(_fullyQualifiedFormat); |
54 | 45 | var key = $"F:{typeFqn}.{field.Name}"; |
55 | | - if (_fieldNamesByKey.TryGetValue(key, out var fieldName)) |
56 | | - return fieldName; |
| 46 | + if (_expressionsByKey.TryGetValue(key, out var cached)) |
| 47 | + return cached; |
57 | 48 |
|
58 | | - fieldName = $"_{_prefix}f{_fieldCounter++}"; |
59 | 49 | var flags = field.IsStatic |
60 | 50 | ? "global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static" |
61 | 51 | : "global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance"; |
62 | | - var declaration = $"""private static readonly global::System.Reflection.FieldInfo {fieldName} = typeof({typeFqn}).GetField("{field.Name}", {flags});"""; |
63 | | - _fieldNamesByKey[key] = fieldName; |
64 | | - _declarations.Add(declaration); |
65 | | - return fieldName; |
| 52 | + var expr = $"typeof({typeFqn}).GetField(\"{field.Name}\", {flags})"; |
| 53 | + _expressionsByKey[key] = expr; |
| 54 | + return expr; |
66 | 55 | } |
67 | 56 |
|
68 | 57 | /// <summary> |
69 | | - /// Returns the field name for a cached <see cref="System.Reflection.MethodInfo"/>, |
70 | | - /// creating the declaration if this method hasn't been seen before. |
| 58 | + /// Returns an inline reflection expression for a <see cref="System.Reflection.MethodInfo"/>. |
71 | 59 | /// </summary> |
72 | 60 | public string EnsureMethodInfo(IMethodSymbol method) |
73 | 61 | { |
74 | 62 | var typeFqn = method.ContainingType.ToDisplayString(_fullyQualifiedFormat); |
75 | 63 | var paramTypes = string.Join(", ", method.Parameters.Select(p => |
76 | 64 | $"typeof({p.Type.ToDisplayString(_fullyQualifiedFormat)})")); |
77 | 65 | var key = $"M:{typeFqn}.{method.Name}({paramTypes})"; |
78 | | - if (_fieldNamesByKey.TryGetValue(key, out var fieldName)) |
79 | | - return fieldName; |
| 66 | + if (_expressionsByKey.TryGetValue(key, out var cached)) |
| 67 | + return cached; |
80 | 68 |
|
81 | | - fieldName = $"_{_prefix}m{_methodCounter++}"; |
82 | 69 | var flags = method.IsStatic |
83 | 70 | ? "global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static" |
84 | 71 | : "global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance"; |
85 | 72 |
|
86 | | - string declaration; |
| 73 | + string expr; |
87 | 74 | if (method.IsGenericMethod) |
88 | 75 | { |
89 | | - // Generic methods: find by name + generic arity + param count, then MakeGenericMethod. |
90 | | - // We can't use GetMethod with parameter types because the definition's parameters |
91 | | - // reference its own type parameters (e.g. IEnumerable<TSource>) which aren't valid C# types. |
92 | 76 | var originalDef = method.OriginalDefinition; |
93 | 77 | var genericArity = originalDef.TypeParameters.Length; |
94 | 78 | var paramCount = originalDef.Parameters.Length; |
95 | 79 | var typeArgs = string.Join(", ", method.TypeArguments.Select(t => |
96 | 80 | $"typeof({t.ToDisplayString(_fullyQualifiedFormat)})")); |
97 | | - declaration = $"private static readonly global::System.Reflection.MethodInfo {fieldName} = global::System.Linq.Enumerable.First(global::System.Linq.Enumerable.Where(typeof({typeFqn}).GetMethods({flags}), m => m.Name == \"{method.Name}\" && m.IsGenericMethodDefinition && m.GetGenericArguments().Length == {genericArity} && m.GetParameters().Length == {paramCount})).MakeGenericMethod({typeArgs});"; |
| 81 | + expr = $"global::System.Linq.Enumerable.First(global::System.Linq.Enumerable.Where(typeof({typeFqn}).GetMethods({flags}), m => m.Name == \"{method.Name}\" && m.IsGenericMethodDefinition && m.GetGenericArguments().Length == {genericArity} && m.GetParameters().Length == {paramCount})).MakeGenericMethod({typeArgs})"; |
98 | 82 | } |
99 | 83 | else |
100 | 84 | { |
101 | | - declaration = $"private static readonly global::System.Reflection.MethodInfo {fieldName} = typeof({typeFqn}).GetMethod(\"{method.Name}\", {flags}, null, new global::System.Type[] {{ {paramTypes} }}, null);"; |
| 85 | + expr = $"typeof({typeFqn}).GetMethod(\"{method.Name}\", {flags}, null, new global::System.Type[] {{ {paramTypes} }}, null)"; |
102 | 86 | } |
103 | 87 |
|
104 | | - _fieldNamesByKey[key] = fieldName; |
105 | | - _declarations.Add(declaration); |
106 | | - return fieldName; |
| 88 | + _expressionsByKey[key] = expr; |
| 89 | + return expr; |
107 | 90 | } |
108 | 91 |
|
109 | 92 | /// <summary> |
110 | | - /// Returns the field name for a cached <see cref="System.Reflection.ConstructorInfo"/>, |
111 | | - /// creating the declaration if this constructor hasn't been seen before. |
| 93 | + /// Returns an inline reflection expression for a <see cref="System.Reflection.ConstructorInfo"/>. |
112 | 94 | /// </summary> |
113 | 95 | public string EnsureConstructorInfo(IMethodSymbol constructor) |
114 | 96 | { |
115 | 97 | var typeFqn = constructor.ContainingType.ToDisplayString(_fullyQualifiedFormat); |
116 | 98 | var paramTypes = string.Join(", ", constructor.Parameters.Select(p => |
117 | 99 | $"typeof({p.Type.ToDisplayString(_fullyQualifiedFormat)})")); |
118 | 100 | var key = $"C:{typeFqn}({paramTypes})"; |
119 | | - if (_fieldNamesByKey.TryGetValue(key, out var fieldName)) |
120 | | - return fieldName; |
| 101 | + if (_expressionsByKey.TryGetValue(key, out var cached)) |
| 102 | + return cached; |
121 | 103 |
|
122 | | - fieldName = $"_{_prefix}c{_constructorCounter++}"; |
123 | | - var declaration = $"private static readonly global::System.Reflection.ConstructorInfo {fieldName} = typeof({typeFqn}).GetConstructor(new global::System.Type[] {{ {paramTypes} }});"; |
124 | | - _fieldNamesByKey[key] = fieldName; |
125 | | - _declarations.Add(declaration); |
126 | | - return fieldName; |
| 104 | + var expr = $"typeof({typeFqn}).GetConstructor(new global::System.Type[] {{ {paramTypes} }})"; |
| 105 | + _expressionsByKey[key] = expr; |
| 106 | + return expr; |
127 | 107 | } |
128 | 108 |
|
129 | 109 | /// <summary> |
130 | | - /// Returns all generated <c>private static readonly</c> field declarations. |
| 110 | + /// Returns all static field declarations. Always empty since reflection is now inlined. |
131 | 111 | /// </summary> |
132 | 112 | public IReadOnlyList<string> GetDeclarations() |
133 | 113 | { |
134 | | - return _declarations; |
| 114 | + return Array.Empty<string>(); |
135 | 115 | } |
136 | 116 | } |
0 commit comments