Skip to content

Commit 18ddd62

Browse files
authored
Merge pull request #643 from stakx/bug/covariant-returns
Improve support for covariant returns (where generic types are involved)
2 parents 451c58f + 6b16135 commit 18ddd62

File tree

7 files changed

+94
-15
lines changed

7 files changed

+94
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
Bugfixes:
6+
- Proxies using records derived from a base generic record broken using .NET 6 compiler (@CesarD, #632)
67
- Failure proxying type that has a non-inheritable custom attribute type applied where `null` argument is given for array parameter (@stakx, #637)
78
- Nested custom attribute types do not get replicated (@stakx, #638)
89

src/Castle.Core.Tests/DynamicProxy.Tests/CovariantReturnsTestCase.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public void Reflection_returns_methods_from_a_derived_class_before_methods_from_
5252
[TestCase(typeof(DerivedClassWithStringInsteadOfObject))]
5353
[TestCase(typeof(DerivedClassWithStringInsteadOfInterface))]
5454
[TestCase(typeof(DerivedClassWithStringInsteadOfGenericArg))]
55+
[TestCase(typeof(BottleOfWater))]
5556
public void Can_proxy_type_having_method_with_covariant_return(Type classToProxy)
5657
{
5758
_ = generator.CreateClassProxy(classToProxy, new StandardInterceptor());
@@ -62,6 +63,7 @@ public void Can_proxy_type_having_method_with_covariant_return(Type classToProxy
6263
[TestCase(typeof(DerivedClassWithStringInsteadOfObject), typeof(string))]
6364
[TestCase(typeof(DerivedClassWithStringInsteadOfInterface), typeof(string))]
6465
[TestCase(typeof(DerivedClassWithStringInsteadOfGenericArg), typeof(string))]
66+
[TestCase(typeof(BottleOfWater), typeof(Glass<Water>))]
6567
public void Proxied_method_has_correct_return_type(Type classToProxy, Type expectedMethodReturnType)
6668
{
6769
var proxy = generator.CreateClassProxy(classToProxy, new StandardInterceptor());
@@ -116,6 +118,22 @@ public class DerivedClassWithStringInsteadOfGenericArg : BaseClassWithGenericArg
116118
{
117119
public override string Method() => nameof(DerivedClassWithStringInsteadOfGenericArg);
118120
}
121+
122+
public interface Glass<out T> { }
123+
124+
public class Liquid { }
125+
126+
public class Water : Liquid { }
127+
128+
public class BottleOfLiquid
129+
{
130+
public virtual Glass<Liquid> Method() => default(Glass<Liquid>);
131+
}
132+
133+
public class BottleOfWater : BottleOfLiquid
134+
{
135+
public override Glass<Water> Method() => default(Glass<Water>);
136+
}
119137
}
120138
}
121139

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2004-2022 Castle Project - http://www.castleproject.org/
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
namespace Castle.DynamicProxy.Tests.Records
16+
{
17+
public record DerivedFromEmptyGenericRecord : EmptyGenericRecord<object>
18+
{
19+
}
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2004-2022 Castle Project - http://www.castleproject.org/
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
namespace Castle.DynamicProxy.Tests.Records
16+
{
17+
public record DerivedFromEmptyRecord : EmptyRecord
18+
{
19+
}
20+
}

src/Castle.Core.Tests/DynamicProxy.Tests/Records/DerivedEmptyRecord.cs renamed to src/Castle.Core.Tests/DynamicProxy.Tests/Records/EmptyGenericRecord.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
namespace Castle.DynamicProxy.Tests.Records
1616
{
17-
public record DerivedEmptyRecord : EmptyRecord
17+
public record EmptyGenericRecord<T>
1818
{
1919
}
2020
}

src/Castle.Core.Tests/DynamicProxy.Tests/RecordsTestCase.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,21 @@ public void Can_proxy_empty_record()
2828
}
2929

3030
[Test]
31-
public void Can_proxy_derived_empty_record()
31+
public void Can_proxy_record_derived_from_empty_record()
3232
{
33-
_ = generator.CreateClassProxy<DerivedEmptyRecord>(new StandardInterceptor());
33+
_ = generator.CreateClassProxy<DerivedFromEmptyRecord>(new StandardInterceptor());
34+
}
35+
36+
[Test]
37+
public void Can_proxy_empty_generic_record()
38+
{
39+
_ = generator.CreateClassProxy<EmptyGenericRecord<object>>(new StandardInterceptor());
40+
}
41+
42+
[Test]
43+
public void Can_proxy_record_derived_from_empty_generic_record()
44+
{
45+
_ = generator.CreateClassProxy<DerivedFromEmptyGenericRecord>(new StandardInterceptor());
3446
}
3547
}
3648
}

src/Castle.Core/DynamicProxy/Generators/MethodSignatureComparer.cs

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,27 @@ public bool EqualParameters(MethodInfo x, MethodInfo y)
8181

8282
public bool EqualReturnTypes(MethodInfo x, MethodInfo y)
8383
{
84-
return EqualSignatureTypes(x.ReturnType, y.ReturnType, x, y);
84+
var xr = x.ReturnType;
85+
var yr = y.ReturnType;
86+
87+
if (EqualSignatureTypes(xr, yr))
88+
{
89+
return true;
90+
}
91+
92+
// This enables covariant method returns for .NET 5 and newer.
93+
// No need to check for runtime support, since such methods are marked with a custom attribute;
94+
// see https://github.com/dotnet/runtime/blob/main/docs/design/features/covariant-return-methods.md.
95+
if (preserveBaseOverridesAttribute != null)
96+
{
97+
return (x.IsDefined(preserveBaseOverridesAttribute, inherit: false) && yr.IsAssignableFrom(xr))
98+
|| (y.IsDefined(preserveBaseOverridesAttribute, inherit: false) && xr.IsAssignableFrom(yr));
99+
}
100+
101+
return false;
85102
}
86103

87-
private bool EqualSignatureTypes(Type x, Type y, MethodInfo xm = null, MethodInfo ym = null)
104+
private bool EqualSignatureTypes(Type x, Type y)
88105
{
89106
if (x.IsGenericParameter != y.IsGenericParameter)
90107
{
@@ -122,22 +139,13 @@ private bool EqualSignatureTypes(Type x, Type y, MethodInfo xm = null, MethodInf
122139

123140
for (var i = 0; i < xArgs.Length; ++i)
124141
{
125-
if(!EqualSignatureTypes(xArgs[i], yArgs[i])) return false;
142+
if(!EqualSignatureTypes(xArgs[i], yArgs[i])) return false;
126143
}
127144
}
128145
else
129146
{
130147
if (!x.Equals(y))
131148
{
132-
// This enables covariant method returns for .NET 5 and newer.
133-
// No need to check for runtime support, since such methods are marked with a custom attribute;
134-
// see https://github.com/dotnet/runtime/blob/main/docs/design/features/covariant-return-methods.md.
135-
if (preserveBaseOverridesAttribute != null)
136-
{
137-
return (xm != null && xm.IsDefined(preserveBaseOverridesAttribute, inherit: false) && y.IsAssignableFrom(x))
138-
|| (ym != null && ym.IsDefined(preserveBaseOverridesAttribute, inherit: false) && x.IsAssignableFrom(y));
139-
}
140-
141149
return false;
142150
}
143151
}

0 commit comments

Comments
 (0)