Skip to content

Commit 00ed3c9

Browse files
committed
add workaround for by-ref parameter types
1 parent d8c74d8 commit 00ed3c9

File tree

3 files changed

+57
-41
lines changed

3 files changed

+57
-41
lines changed

src/Smdn.Reflection.ReverseGenerating/Smdn.Reflection.ReverseGenerating/CSharpTypeFormatter.FormatTypeNameWithNullabilityAnnotation.cs

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@ namespace Smdn.Reflection.ReverseGenerating;
1515
#pragma warning disable IDE0040
1616
static partial class CSharpFormatter {
1717
#pragma warning restore IDE0040
18+
// Workaround: The pseudo ParameterInfo type which unwraps 'ByRef' type to its element type
19+
// See https://github.com/dotnet/runtime/issues/72320
20+
private sealed class ByRefElementTypeParameterInfo : ParameterInfo {
21+
public ParameterInfo BaseParameter { get; }
22+
23+
public ByRefElementTypeParameterInfo(ParameterInfo baseParam)
24+
{
25+
BaseParameter = baseParam;
26+
}
27+
28+
public override MemberInfo Member => BaseParameter.Member;
29+
public override Type ParameterType => BaseParameter.ParameterType.GetElementType()!;
30+
public override IList<CustomAttributeData> GetCustomAttributesData()
31+
=> BaseParameter.GetCustomAttributesData();
32+
}
33+
1834
private static StringBuilder FormatTypeNameWithNullabilityAnnotation(
1935
NullabilityInfo target,
2036
StringBuilder builder,
@@ -28,90 +44,91 @@ static string GetNullabilityAnnotation(NullabilityInfo target)
2844
? NullableAnnotationSyntaxString
2945
: string.Empty;
3046

31-
Type? byRefParameterType = null;
32-
33-
if (target.Type.IsByRef && options.AttributeProvider is ParameterInfo p) {
34-
// retval/parameter modifiers
35-
if (p.IsIn)
36-
builder.Append("in ");
37-
else if (p.IsOut)
38-
builder.Append("out ");
39-
else /*if (p.IsRetval)*/
40-
builder.Append("ref ");
47+
if (target.Type.IsByRef) {
48+
var elementTypeNullabilityInfo = target.ElementType;
49+
50+
if (options.AttributeProvider is ParameterInfo p) {
51+
// retval/parameter modifiers
52+
if (p.IsIn)
53+
builder.Append("in ");
54+
else if (p.IsOut)
55+
builder.Append("out ");
56+
else /*if (p.IsRetval)*/
57+
builder.Append("ref ");
58+
59+
// [.net6.0] Currently, NullabilityInfo.ElementType is always null if the type is ByRef.
60+
// Uses the workaround implementation instead in that case.
61+
// See https://github.com/dotnet/runtime/issues/72320
62+
if (target.ElementType is null && p.ParameterType.HasElementType)
63+
elementTypeNullabilityInfo = new NullabilityInfoContext().Create(new ByRefElementTypeParameterInfo(p)); // TODO: context
64+
}
4165

42-
byRefParameterType = target.Type.GetElementTypeOrThrow();
66+
if (elementTypeNullabilityInfo is not null) {
67+
return FormatTypeNameWithNullabilityAnnotation(
68+
elementTypeNullabilityInfo,
69+
builder,
70+
options
71+
);
72+
}
4373
}
4474

45-
if (target.ElementType is not null) {
75+
if (target.Type.IsArray) {
4676
// arrays
47-
return FormatTypeNameWithNullabilityAnnotation(target.ElementType, builder, options)
77+
return FormatTypeNameWithNullabilityAnnotation(target.ElementType!, builder, options)
4878
.Append('[')
4979
.Append(',', target.Type.GetArrayRank() - 1)
5080
.Append(']')
5181
.Append(GetNullabilityAnnotation(target));
5282
}
5383

54-
if (target.Type.IsPointer || (target.Type.IsByRef && options.AttributeProvider is not ParameterInfo))
84+
if (target.Type.IsPointer || target.Type.IsByRef)
85+
// pointer types or ByRef types (exclude ParameterInfo)
5586
return builder.Append(FormatTypeNameCore(target.Type, options));
5687

57-
var targetType = byRefParameterType ?? target.Type;
58-
59-
if (IsValueTupleType(targetType)) {
60-
if (byRefParameterType is not null)
61-
// TODO: cannot get NullabilityInfo of generic type arguments from by-ref parameter type
62-
return builder.Append(FormatTypeNameCore(targetType, options));
63-
88+
if (IsValueTupleType(target.Type)) {
6489
// special case for value tuples (ValueTuple<>)
6590
return FormatValueTupleType(target, builder, options)
6691
.Append(GetNullabilityAnnotation(target));
6792
}
6893

94+
var targetType = target.Type;
6995
var isGenericTypeClosedOrDefinition =
7096
targetType.IsGenericTypeDefinition ||
7197
targetType.IsConstructedGenericType ||
7298
(targetType.IsGenericType && targetType.ContainsGenericParameters);
73-
string? nullabilityAnnotationForByRefParameter = null;
7499

75100
if (Nullable.GetUnderlyingType(targetType) is Type nullableUnderlyingType) {
76-
if (byRefParameterType is not null)
77-
// note: if the by-ref parameter is Nullable<>, NullabilityState will not be Nullable
78-
nullabilityAnnotationForByRefParameter = NullableAnnotationSyntaxString;
79-
80101
// nullable value types (Nullable<>)
81102
if (IsValueTupleType(nullableUnderlyingType)) {
82103
// special case for nullable value tuples (Nullable<ValueTuple<>>)
83104
return FormatValueTupleType(target, builder, options)
84-
.Append(GetNullabilityAnnotation(target))
85-
.Append(nullabilityAnnotationForByRefParameter);
105+
.Append(GetNullabilityAnnotation(target));
86106
}
87107
else if (nullableUnderlyingType.IsGenericType) {
88108
// case for nullable generic value types (Nullable<GenericValueType<>>)
89109
return FormatNullableGenericValueType(target, builder, options)
90-
.Append(GetNullabilityAnnotation(target))
91-
.Append(nullabilityAnnotationForByRefParameter);
110+
.Append(GetNullabilityAnnotation(target));
92111
}
93112

94113
targetType = nullableUnderlyingType;
95114
}
96115
else if (isGenericTypeClosedOrDefinition) {
97116
// other generic types
98-
if (targetType == byRefParameterType) {
117+
if (targetType.IsByRef) {
99118
// TODO: cannot get NullabilityInfo of generic type arguments from by-ref parameter type
100119
return builder.Append(FormatTypeNameCore(targetType, options));
101120
}
102121
else {
103122
return FormatClosedGenericTypeOrGenericTypeDefinition(target, builder, options)
104-
.Append(GetNullabilityAnnotation(target))
105-
.Append(nullabilityAnnotationForByRefParameter);
123+
.Append(GetNullabilityAnnotation(target));
106124
}
107125
}
108126

109127
if (options.TranslateLanguagePrimitiveType && IsLanguagePrimitiveType(targetType, out var n)) {
110128
// language primitive types
111129
return builder
112130
.Append(n)
113-
.Append(GetNullabilityAnnotation(target))
114-
.Append(nullabilityAnnotationForByRefParameter);
131+
.Append(GetNullabilityAnnotation(target));
115132
}
116133

117134
if (targetType.IsGenericParameter && targetType.HasGenericParameterNoConstraints())
@@ -127,8 +144,7 @@ static string GetNullabilityAnnotation(NullabilityInfo target)
127144

128145
return builder
129146
.Append(GetTypeName(targetType, options))
130-
.Append(GetNullabilityAnnotation(target))
131-
.Append(nullabilityAnnotationForByRefParameter);
147+
.Append(GetNullabilityAnnotation(target));
132148
}
133149

134150
private static StringBuilder FormatClosedGenericTypeOrGenericTypeDefinition(

tests/Smdn.Reflection.ReverseGenerating/Smdn.Reflection.ReverseGenerating/Generator.MemberDeclaration.Parameters.NullabilityAnnotations.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,12 @@ class RefModifier {
142142
class GenericTypes {
143143
[MemberDeclarationTestCase($"public void {nameof(ValueTupleOfValueTypeAndRefType)}(out (int X, string Y) p) {{}}")] public void ValueTupleOfValueTypeAndRefType(out (int X, string Y) p) => throw null;
144144
[MemberDeclarationTestCase($"public void {nameof(ValueTupleOfNullableValueTypeAndRefType)}(out (int? X, string Y) p) {{}}")] public void ValueTupleOfNullableValueTypeAndRefType(out (int? X, string Y) p) => throw null;
145-
[SkipTestCase("cannot get NullabilityInfo of generic type arguments from by-ref parameter type")][MemberDeclarationTestCase($"public void {nameof(ValueTupleOfValueTypeAndNullableRefType)}(out (int X, string? Y) p) {{}}")] public void ValueTupleOfValueTypeAndNullableRefType(out (int X, string? Y) p) => throw null;
145+
[MemberDeclarationTestCase($"public void {nameof(ValueTupleOfValueTypeAndNullableRefType)}(out (int X, string? Y) p) {{}}")] public void ValueTupleOfValueTypeAndNullableRefType(out (int X, string? Y) p) => throw null;
146146

147147
[MemberDeclarationTestCase($"public void {nameof(IEnumerableOfValueType)}(out IEnumerable<int> p) {{}}", ParameterWithNamespace = false)] public void IEnumerableOfValueType(out IEnumerable<int> p) => throw null;
148148
[MemberDeclarationTestCase($"public void {nameof(IEnumerableOfNullableValueType)}(out IEnumerable<int?> p) {{}}", ParameterWithNamespace = false)] public void IEnumerableOfNullableValueType(out IEnumerable<int?> p) => throw null;
149149
[MemberDeclarationTestCase($"public void {nameof(IEnumerableOfRefType)}(out IEnumerable<string> p) {{}}", ParameterWithNamespace = false)] public void IEnumerableOfRefType(out IEnumerable<string> p) => throw null;
150-
[SkipTestCase("cannot get NullabilityInfo of generic type arguments from by-ref parameter type")][MemberDeclarationTestCase($"public void {nameof(IEnumerableOfNullableRefType)}(out IEnumerable<string?> p) {{}}", ParameterWithNamespace = false)] public void IEnumerableOfNullableRefType(out IEnumerable<string?> p) => throw null;
150+
[MemberDeclarationTestCase($"public void {nameof(IEnumerableOfNullableRefType)}(out IEnumerable<string?> p) {{}}", ParameterWithNamespace = false)] public void IEnumerableOfNullableRefType(out IEnumerable<string?> p) => throw null;
151151
}
152152
}
153153

tests/Smdn.Reflection.ReverseGenerating/Smdn.Reflection.ReverseGenerating/Generator.MemberDeclaration.ReturnParameters.NullabilityAnnotations.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,12 @@ class Ref {
8989
class GenericTypes {
9090
[MemberDeclarationTestCase($"public ref (int X, string Y) {nameof(ValueTupleOfValueTypeAndRefType)}() {{}}")] public ref (int X, string Y) ValueTupleOfValueTypeAndRefType() => throw null;
9191
[MemberDeclarationTestCase($"public ref (int? X, string Y) {nameof(ValueTupleOfNullableValueTypeAndRefType)}() {{}}")] public ref (int? X, string Y) ValueTupleOfNullableValueTypeAndRefType() => throw null;
92-
[SkipTestCase("cannot get NullabilityInfo of generic type arguments from by-ref parameter type")][MemberDeclarationTestCase($"public ref (int X, string? Y) {nameof(ValueTupleOfValueTypeAndNullableRefType)}() {{}}")] public ref (int X, string? Y) ValueTupleOfValueTypeAndNullableRefType() => throw null;
92+
[MemberDeclarationTestCase($"public ref (int X, string? Y) {nameof(ValueTupleOfValueTypeAndNullableRefType)}() {{}}")] public ref (int X, string? Y) ValueTupleOfValueTypeAndNullableRefType() => throw null;
9393

9494
[MemberDeclarationTestCase($"public ref IEnumerable<int> {nameof(IEnumerableOfValueType)}() {{}}", MemberWithNamespace = false)] public ref IEnumerable<int> IEnumerableOfValueType() => throw null;
9595
[MemberDeclarationTestCase($"public ref IEnumerable<int?> {nameof(IEnumerableOfNullableValueType)}() {{}}", MemberWithNamespace = false)] public ref IEnumerable<int?> IEnumerableOfNullableValueType() => throw null;
9696
[MemberDeclarationTestCase($"public ref IEnumerable<string> {nameof(IEnumerableOfRefType)}() {{}}", MemberWithNamespace = false)] public ref IEnumerable<string> IEnumerableOfRefType() => throw null;
97-
[SkipTestCase("cannot get NullabilityInfo of generic type arguments from by-ref parameter type")][MemberDeclarationTestCase($"public ref IEnumerable<string?> {nameof(IEnumerableOfNullableRefType)}() {{}}", MemberWithNamespace = false)] public ref IEnumerable<string?> IEnumerableOfNullableRefType() => throw null;
97+
[MemberDeclarationTestCase($"public ref IEnumerable<string?> {nameof(IEnumerableOfNullableRefType)}() {{}}", MemberWithNamespace = false)] public ref IEnumerable<string?> IEnumerableOfNullableRefType() => throw null;
9898
}
9999
}
100100
}

0 commit comments

Comments
 (0)