Skip to content

Commit 91e751d

Browse files
committed
add support for formatting ref properties
1 parent 0c2f106 commit 91e751d

File tree

4 files changed

+94
-24
lines changed

4 files changed

+94
-24
lines changed

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

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,22 @@ static string FormatCore(Type t, bool showVariance, FormatTypeNameOptions option
4242
if (t.IsByRef) {
4343
var typeName = FormatCore(t.GetElementTypeOrThrow(), showVariance: false, options);
4444

45-
if (options.AttributeProvider is ParameterInfo p) {
46-
// if (p.IsRetval)
47-
// return "ref " + typeName;
48-
if (p.IsIn)
49-
return "in " + typeName;
50-
else if (p.IsOut)
51-
return "out " + typeName;
52-
else
45+
switch (options.AttributeProvider) {
46+
case ParameterInfo para:
47+
// if (para.IsRetval)
48+
// return "ref " + typeName;
49+
if (para.IsIn)
50+
return "in " + typeName;
51+
else if (para.IsOut)
52+
return "out " + typeName;
53+
else
54+
return "ref " + typeName;
55+
56+
case PropertyInfo p:
5357
return "ref " + typeName;
54-
}
55-
else {
56-
return typeName + "&";
58+
59+
default:
60+
return typeName + "&";
5761
}
5862
}
5963

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

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
#if SYSTEM_REFLECTION_NULLABILITYINFO
44
using System;
55
using System.Collections.Generic;
6+
#if WORKAROUND_NULLABILITYINFO_BYREFTYPE
7+
using System.Globalization;
8+
#endif
69
using System.Linq;
710
using System.Reflection;
811
using System.Runtime.CompilerServices;
@@ -32,6 +35,35 @@ public ByRefElementTypeParameterInfo(ParameterInfo baseParam)
3235
public override IList<CustomAttributeData> GetCustomAttributesData()
3336
=> BaseParameter.GetCustomAttributesData();
3437
}
38+
39+
private sealed class ByRefElementTypePropertyInfo : PropertyInfo {
40+
public PropertyInfo BaseProperty { get; }
41+
42+
public ByRefElementTypePropertyInfo(PropertyInfo baseProperty)
43+
{
44+
BaseProperty = baseProperty;
45+
}
46+
47+
public override string Name => BaseProperty.Name;
48+
public override PropertyAttributes Attributes => BaseProperty.Attributes;
49+
public override bool CanRead => BaseProperty.CanRead;
50+
public override bool CanWrite => BaseProperty.CanWrite;
51+
public override Type PropertyType => BaseProperty.PropertyType.GetElementType()!;
52+
public override Type? DeclaringType => BaseProperty.DeclaringType;
53+
public override Type? ReflectedType => BaseProperty.ReflectedType;
54+
public override IList<CustomAttributeData> GetCustomAttributesData() => BaseProperty.GetCustomAttributesData();
55+
public override object[] GetCustomAttributes(bool inherit) => BaseProperty.GetCustomAttributes(inherit);
56+
public override object[] GetCustomAttributes(Type attributeType, bool inherit) => BaseProperty.GetCustomAttributes(attributeType, inherit);
57+
public override bool IsDefined(Type attributeType, bool inherit) => BaseProperty.IsDefined(attributeType, inherit);
58+
public override MethodInfo[] GetAccessors(bool nonPublic) => BaseProperty.GetAccessors(nonPublic);
59+
public override MethodInfo? GetGetMethod(bool nonPublic) => BaseProperty.GetGetMethod(nonPublic);
60+
public override MethodInfo? GetSetMethod(bool nonPublic) => BaseProperty.GetSetMethod(nonPublic);
61+
public override ParameterInfo[] GetIndexParameters() => BaseProperty.GetIndexParameters();
62+
public override object? GetValue(object? obj, BindingFlags invokeAttr, Binder? binder, object?[]? index, CultureInfo? culture)
63+
=> BaseProperty.GetValue(obj, invokeAttr, binder, index, culture);
64+
public override void SetValue(object? obj, object? value, BindingFlags invokeAttr, Binder? binder, object?[]? index, CultureInfo? culture)
65+
=> BaseProperty.SetValue(obj, value, invokeAttr, binder, index, culture);
66+
}
3567
#endif
3668

3769
private static StringBuilder FormatTypeNameWithNullabilityAnnotation(
@@ -52,22 +84,33 @@ static string GetNullabilityAnnotation(NullabilityInfo target)
5284
var elementTypeNullabilityInfo = target.ElementType;
5385
#endif
5486

55-
if (options.AttributeProvider is ParameterInfo p) {
56-
// retval/parameter modifiers
57-
if (p.IsIn)
58-
builder.Append("in ");
59-
else if (p.IsOut)
60-
builder.Append("out ");
61-
else /*if (p.IsRetval)*/
87+
switch (options.AttributeProvider) {
88+
case ParameterInfo para:
89+
// retval/parameter modifiers
90+
if (para.IsIn)
91+
builder.Append("in ");
92+
else if (para.IsOut)
93+
builder.Append("out ");
94+
else /*if (para.IsRetval)*/
95+
builder.Append("ref ");
96+
97+
#if WORKAROUND_NULLABILITYINFO_BYREFTYPE
98+
// [.net6.0] Currently, NullabilityInfo.ElementType is always null if the type is ByRef.
99+
// Uses the workaround implementation instead in that case.
100+
// See https://github.com/dotnet/runtime/issues/72320
101+
if (options.NullabilityInfoContext is not null && target.ElementType is null && para.ParameterType.HasElementType)
102+
elementTypeNullabilityInfo = options.NullabilityInfoContext.Create(new ByRefElementTypeParameterInfo(para));
103+
#endif
104+
break;
105+
106+
case PropertyInfo p:
62107
builder.Append("ref ");
63108

64109
#if WORKAROUND_NULLABILITYINFO_BYREFTYPE
65-
// [.net6.0] Currently, NullabilityInfo.ElementType is always null if the type is ByRef.
66-
// Uses the workaround implementation instead in that case.
67-
// See https://github.com/dotnet/runtime/issues/72320
68-
if (options.NullabilityInfoContext is not null && target.ElementType is null && p.ParameterType.HasElementType)
69-
elementTypeNullabilityInfo = options.NullabilityInfoContext.Create(new ByRefElementTypeParameterInfo(p));
110+
if (options.NullabilityInfoContext is not null && target.ElementType is null && p.PropertyType.HasElementType)
111+
elementTypeNullabilityInfo = options.NullabilityInfoContext.Create(new ByRefElementTypePropertyInfo(p));
70112
#endif
113+
break;
71114
}
72115

73116
#if WORKAROUND_NULLABILITYINFO_BYREFTYPE
@@ -97,7 +140,7 @@ static string GetNullabilityAnnotation(NullabilityInfo target)
97140
}
98141

99142
if (target.Type.IsPointer || target.Type.IsByRef)
100-
// pointer types or ByRef types (exclude ParameterInfo)
143+
// pointer types or ByRef types (exclude ParameterInfo and PropertyInfo)
101144
return builder.Append(FormatTypeNameCore(target.Type, options));
102145

103146
if (IsValueTupleType(target.Type)) {

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,24 @@ public class NullabilityAnnotations {
108108
[MemberDeclarationTestCase($"public Dictionary<string, string>? {nameof(DictionaryOfRefTypeValueNullable)} {{ get; }}", MemberWithNamespace = false)] public Dictionary<string, string>? DictionaryOfRefTypeValueNullable => throw null;
109109
[MemberDeclarationTestCase($"public Dictionary<string, string?>? {nameof(NullableDictionaryOfNullableRefTypeValue)} {{ get; }}", MemberWithNamespace = false)] public Dictionary<string, string?>? NullableDictionaryOfNullableRefTypeValue => throw null;
110110

111+
public class RefReturnTypes {
112+
[MemberDeclarationTestCase($"public ref int {nameof(ValueType)} {{ get; }}")] public ref int ValueType => throw null;
113+
[MemberDeclarationTestCase($"public ref int? {nameof(NullableValueType)} {{ get; }}")] public ref int? NullableValueType => throw null;
114+
115+
[MemberDeclarationTestCase($"public ref string {nameof(RefType)} {{ get; }}")] public ref string RefType => throw null;
116+
[MemberDeclarationTestCase($"public ref string? {nameof(NullableRefType)} {{ get; }}")] public ref string? NullableRefType => throw null;
117+
118+
[MemberDeclarationTestCase($"public ref (int, int) {nameof(ValueTupleOfValueType)} {{ get; }}")] public ref (int, int) ValueTupleOfValueType => throw null;
119+
[MemberDeclarationTestCase($"public ref (int, int?) {nameof(ValueTupleOfNullableValueType)} {{ get; }}")] public ref (int, int?) ValueTupleOfNullableValueType => throw null;
120+
[MemberDeclarationTestCase($"public ref (int, int)? {nameof(NullableValueTupleOfValueType)} {{ get; }}")] public ref (int, int)? NullableValueTupleOfValueType => throw null;
121+
[MemberDeclarationTestCase($"public ref (int, int?)? {nameof(NullableValueTupleOfNullableValueType)} {{ get; }}")] public ref (int, int?)? NullableValueTupleOfNullableValueType => throw null;
122+
123+
[MemberDeclarationTestCase($"public ref (int, string) {nameof(ValueTupleOfRefType)} {{ get; }}")] public ref (int, string) ValueTupleOfRefType => throw null;
124+
[MemberDeclarationTestCase($"public ref (int, string?) {nameof(ValueTupleOfNullableRefType)} {{ get; }}")] public ref (int, string?) ValueTupleOfNullableRefType => throw null;
125+
[MemberDeclarationTestCase($"public ref (int, string)? {nameof(NullableValueTupleOfRefType)} {{ get; }}")] public ref (int, string)? NullableValueTupleOfRefType => throw null;
126+
[MemberDeclarationTestCase($"public ref (int, string?)? {nameof(NullableValueTupleOfNullableRefType)} {{ get; }}")] public ref (int, string?)? NullableValueTupleOfNullableRefType => throw null;
127+
}
128+
111129
public class Indexers {
112130
public class IndexTypes {
113131
public abstract class ValueType {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,11 @@ public class ValueTupleTypes {
456456
[MemberDeclarationTestCase("public System.ValueTuple<int> P4 { get; }")] public ValueTuple<int> P4 => throw null;
457457
}
458458

459+
public class RefReturnTypes {
460+
[MemberDeclarationTestCase($"public ref int {nameof(PRefInt)} {{ get; }}")] public ref int PRefInt => throw null;
461+
[MemberDeclarationTestCase($"public ref string {nameof(PRefString)} {{ get; }}")] public ref string PRefString => throw null;
462+
}
463+
459464
public class Accessors {
460465
[MemberDeclarationTestCase("public int P1 { get; set; }")] public int P1 { get; set; }
461466
[MemberDeclarationTestCase("public int P2 { get; }")] public int P2 { get; }

0 commit comments

Comments
 (0)