Skip to content

Commit f7f55c7

Browse files
authored
Merge pull request #4062 from sharwell/covariant-return
Updates for covariant return types in C# 9
2 parents 20ccead + 1ed6475 commit f7f55c7

File tree

5 files changed

+248
-1
lines changed

5 files changed

+248
-1
lines changed

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/DocumentationRules/SA1615CSharp9UnitTests.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,73 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp9.DocumentationRules
55
{
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.CodeAnalysis.Testing;
69
using StyleCop.Analyzers.Test.CSharp8.DocumentationRules;
10+
using Xunit;
11+
using static StyleCop.Analyzers.Test.Verifiers.StyleCopDiagnosticVerifier<StyleCop.Analyzers.DocumentationRules.SA1615ElementReturnValueMustBeDocumented>;
712

813
public partial class SA1615CSharp9UnitTests : SA1615CSharp8UnitTests
914
{
15+
[Fact]
16+
[WorkItem(3975, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3975")]
17+
public async Task TestCovariantOverrideMissingReturnsDocumentationAsync()
18+
{
19+
var testCode = @"
20+
public class BaseType
21+
{
22+
}
23+
24+
public class DerivedType : BaseType
25+
{
26+
}
27+
28+
public class BaseClass
29+
{
30+
/// <summary>Creates a base instance.</summary>
31+
/// <returns>A <see cref=""BaseType""/> instance.</returns>
32+
public virtual BaseType Create() => new BaseType();
33+
}
34+
35+
public class DerivedClass : BaseClass
36+
{
37+
/// <summary>Creates a derived instance.</summary>
38+
public override [|DerivedType|] Create() => new DerivedType();
39+
}
40+
";
41+
42+
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
43+
}
44+
45+
[Fact]
46+
[WorkItem(3975, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3975")]
47+
public async Task TestCovariantOverrideInheritsReturnsDocumentationAsync()
48+
{
49+
var testCode = @"
50+
public class BaseType
51+
{
52+
}
53+
54+
public class DerivedType : BaseType
55+
{
56+
}
57+
58+
public class BaseClass
59+
{
60+
/// <summary>Creates a base instance.</summary>
61+
/// <returns>A <see cref=""BaseType""/> instance.</returns>
62+
public virtual BaseType Create() => new BaseType();
63+
}
64+
65+
public class DerivedClass : BaseClass
66+
{
67+
/// <inheritdoc/>
68+
public override DerivedType Create() => new DerivedType();
69+
}
70+
";
71+
72+
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
73+
}
1074
}
1175
}

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/OrderingRules/SA1206CSharp9UnitTests.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,62 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp9.OrderingRules
55
{
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.CodeAnalysis.Testing;
69
using StyleCop.Analyzers.Test.CSharp8.OrderingRules;
10+
using Xunit;
11+
using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier<
12+
StyleCop.Analyzers.OrderingRules.SA1206DeclarationKeywordsMustFollowOrder,
13+
StyleCop.Analyzers.OrderingRules.SA1206CodeFixProvider>;
714

815
public partial class SA1206CSharp9UnitTests : SA1206CSharp8UnitTests
916
{
17+
[Fact]
18+
[WorkItem(3975, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3975")]
19+
public async Task TestCovariantOverrideKeywordsOutOfOrderAsync()
20+
{
21+
var testCode = @"
22+
public class BaseType
23+
{
24+
}
25+
26+
public class DerivedType : BaseType
27+
{
28+
}
29+
30+
public class BaseClass
31+
{
32+
public virtual BaseType Create() => new BaseType();
33+
}
34+
35+
public class DerivedClass : BaseClass
36+
{
37+
override [|public|] DerivedType Create() => new DerivedType();
38+
}
39+
";
40+
41+
var fixedCode = @"
42+
public class BaseType
43+
{
44+
}
45+
46+
public class DerivedType : BaseType
47+
{
48+
}
49+
50+
public class BaseClass
51+
{
52+
public virtual BaseType Create() => new BaseType();
53+
}
54+
55+
public class DerivedClass : BaseClass
56+
{
57+
public override DerivedType Create() => new DerivedType();
58+
}
59+
";
60+
61+
await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(false);
62+
}
1063
}
1164
}

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/ReadabilityRules/SA1100CSharp9UnitTests.cs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,110 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp9.ReadabilityRules
55
{
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.CodeAnalysis.Testing;
69
using StyleCop.Analyzers.Test.CSharp8.ReadabilityRules;
10+
using StyleCop.Analyzers.Test.Helpers;
11+
using Xunit;
12+
using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier<
13+
StyleCop.Analyzers.ReadabilityRules.SA1100DoNotPrefixCallsWithBaseUnlessLocalImplementationExists,
14+
StyleCop.Analyzers.ReadabilityRules.SA1100CodeFixProvider>;
715

816
public partial class SA1100CSharp9UnitTests : SA1100CSharp8UnitTests
917
{
18+
[Fact]
19+
[WorkItem(3975, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3975")]
20+
public async Task TestCovariantOverrideAllowsBaseCallWhenOverrideExistsAsync()
21+
{
22+
var testCode = @"
23+
public class BaseType
24+
{
25+
}
26+
27+
public class DerivedType : BaseType
28+
{
29+
}
30+
31+
public class BaseClass
32+
{
33+
protected virtual BaseType Create() => new BaseType();
34+
}
35+
36+
public class DerivedClass : BaseClass
37+
{
38+
protected override DerivedType Create()
39+
{
40+
var value = base.Create();
41+
return new DerivedType();
42+
}
43+
}
44+
";
45+
46+
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
47+
}
48+
49+
[Fact]
50+
[WorkItem(3975, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3975")]
51+
public async Task TestBaseCallFlaggedWhenOverrideOnlyInIntermediateTypeAsync()
52+
{
53+
var testCode = @"
54+
public class BaseType
55+
{
56+
}
57+
58+
public class DerivedType : BaseType
59+
{
60+
}
61+
62+
public class BaseClass
63+
{
64+
protected virtual BaseType Create() => new BaseType();
65+
}
66+
67+
public class IntermediateClass : BaseClass
68+
{
69+
protected override DerivedType Create() => new DerivedType();
70+
}
71+
72+
public class DerivedClass : IntermediateClass
73+
{
74+
public BaseType CreateThroughBase()
75+
{
76+
return [|base|].Create();
77+
}
78+
}
79+
";
80+
81+
var fixedCode = @"
82+
public class BaseType
83+
{
84+
}
85+
86+
public class DerivedType : BaseType
87+
{
88+
}
89+
90+
public class BaseClass
91+
{
92+
protected virtual BaseType Create() => new BaseType();
93+
}
94+
95+
public class IntermediateClass : BaseClass
96+
{
97+
protected override DerivedType Create() => new DerivedType();
98+
}
99+
100+
public class DerivedClass : IntermediateClass
101+
{
102+
public BaseType CreateThroughBase()
103+
{
104+
return this.Create();
105+
}
106+
}
107+
";
108+
109+
await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(false);
110+
}
10111
}
11112
}

documentation/SA1100.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,14 @@ public override string JoinName(string first, string last)
4444
}
4545
```
4646

47-
At this point, the local call to `base.JoinName(...)` most likely introduces a bug into the code. This call will always call the base class method and will cause the local override to be ignored.
47+
At this point, the local call to `base.JoinName(...)` most likely introduces a bug into the code. This call will always call the base class method and will cause the local override to be ignored.
4848

4949
For this reason, calls to members from a base class should not begin with `base.`, unless a local override is implemented, and the developer wants to specifically call the base class member. When there is no local override of the base class member, the call should be prefixed with `this.` rather than `base.`.
5050

51+
### Covariant return types
52+
53+
Starting in C# 9, overrides can return a more derived type than the base member. A `base.` call still bypasses the most-derived override, even when an intermediate class provides a covariant implementation. SA1100 continues to report `base.` calls from a class that does not declare its own override, and the suggested fix remains to call the member through `this.` instead.
54+
5155
## How to fix violations
5256

5357
To fix a violation of this rule, change the `base.` prefix to `this.`.

documentation/SA1600.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,31 @@ class Car : Vehicle
8383
}
8484
```
8585

86+
Starting with C# 9, overrides can declare covariant return types. Documentation can still be inherited directly from the base member using `<inheritdoc/>`:
87+
88+
```csharp
89+
/// <summary>
90+
/// Base factory.
91+
/// </summary>
92+
class WidgetFactory
93+
{
94+
/// <summary>
95+
/// Creates a widget.
96+
/// </summary>
97+
/// <returns>The newly created <see cref="Widget"/>.</returns>
98+
public virtual Widget Create() => new Widget();
99+
}
100+
101+
/// <summary>
102+
/// Specialized factory.
103+
/// </summary>
104+
class FancyWidgetFactory : WidgetFactory
105+
{
106+
/// <inheritdoc/>
107+
public override FancyWidget Create() => new FancyWidget();
108+
}
109+
```
110+
86111
## How to suppress violations
87112

88113
```csharp

0 commit comments

Comments
 (0)