Skip to content

Commit 85e537a

Browse files
Fix override implementation with generics and constraints (#76223)
2 parents c1c1c0b + 8c15136 commit 85e537a

File tree

4 files changed

+232
-19
lines changed

4 files changed

+232
-19
lines changed

src/Analyzers/CSharp/CodeFixes/ImplementAbstractClass/CSharpImplementAbstractClassCodeFixProvider.cs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,13 @@ namespace Microsoft.CodeAnalysis.CSharp.ImplementAbstractClass;
1212

1313
[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.ImplementAbstractClass), Shared]
1414
[ExtensionOrder(After = PredefinedCodeFixProviderNames.GenerateType)]
15-
internal sealed class CSharpImplementAbstractClassCodeFixProvider :
16-
AbstractImplementAbstractClassCodeFixProvider<TypeDeclarationSyntax>
15+
[method: ImportingConstructor]
16+
[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
17+
internal sealed class CSharpImplementAbstractClassCodeFixProvider()
18+
: AbstractImplementAbstractClassCodeFixProvider<TypeDeclarationSyntax>(CS0534)
1719
{
1820
private const string CS0534 = nameof(CS0534); // 'Program' does not implement inherited abstract member 'Goo.bar()'
1921

20-
[ImportingConstructor]
21-
[SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
22-
public CSharpImplementAbstractClassCodeFixProvider()
23-
: base(CS0534)
24-
{
25-
}
26-
2722
protected override SyntaxToken GetClassIdentifier(TypeDeclarationSyntax classNode)
2823
=> classNode.Identifier;
2924
}

src/Analyzers/CSharp/Tests/ImplementAbstractClass/ImplementAbstractClassTests.cs

Lines changed: 179 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.Diagnostics.CodeAnalysis;
56
using System.Threading.Tasks;
67
using Microsoft.CodeAnalysis.CodeFixes;
78
using Microsoft.CodeAnalysis.CodeStyle;
@@ -38,8 +39,8 @@ private OptionsCollection AllOptionsOff
3839
};
3940

4041
internal Task TestAllOptionsOffAsync(
41-
string initialMarkup,
42-
string expectedMarkup,
42+
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string initialMarkup,
43+
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string expectedMarkup,
4344
int index = 0,
4445
OptionsCollection? options = null,
4546
ParseOptions? parseOptions = null)
@@ -2553,4 +2554,180 @@ protected override void M()
25532554
}
25542555
""");
25552556
}
2557+
2558+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71225")]
2559+
public async Task TestConstrainedTypeParameter1()
2560+
{
2561+
await TestAllOptionsOffAsync(
2562+
"""
2563+
#nullable enable
2564+
using System;
2565+
2566+
interface I<out T> { }
2567+
2568+
class C { }
2569+
2570+
abstract class Problem
2571+
{
2572+
protected abstract void M<T>(I<T?> i) where T : C;
2573+
}
2574+
2575+
class [|Bad|] : Problem
2576+
{
2577+
}
2578+
""",
2579+
"""
2580+
#nullable enable
2581+
using System;
2582+
2583+
interface I<out T> { }
2584+
2585+
class C { }
2586+
2587+
abstract class Problem
2588+
{
2589+
protected abstract void M<T>(I<T?> i) where T : C;
2590+
}
2591+
2592+
class Bad : Problem
2593+
{
2594+
protected override void M<T>(I<T?> i) where T : class
2595+
{
2596+
throw new NotImplementedException();
2597+
}
2598+
}
2599+
""");
2600+
}
2601+
2602+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71225")]
2603+
public async Task TestConstrainedTypeParameter2()
2604+
{
2605+
await TestAllOptionsOffAsync(
2606+
"""
2607+
#nullable enable
2608+
using System;
2609+
2610+
interface I<out T> { }
2611+
2612+
class C { }
2613+
2614+
abstract class Problem<U>
2615+
{
2616+
protected abstract void M<T>(I<T?> i) where T : U;
2617+
}
2618+
2619+
class [|Bad|] : Problem<int>
2620+
{
2621+
}
2622+
""",
2623+
"""
2624+
#nullable enable
2625+
using System;
2626+
2627+
interface I<out T> { }
2628+
2629+
class C { }
2630+
2631+
abstract class Problem<U>
2632+
{
2633+
protected abstract void M<T>(I<T?> i) where T : U;
2634+
}
2635+
2636+
class Bad : Problem<int>
2637+
{
2638+
protected override void M<T>(I<T> i) where T : struct
2639+
{
2640+
throw new NotImplementedException();
2641+
}
2642+
}
2643+
""");
2644+
}
2645+
2646+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71225")]
2647+
public async Task TestConstrainedTypeParameter3()
2648+
{
2649+
await TestAllOptionsOffAsync(
2650+
"""
2651+
#nullable enable
2652+
using System;
2653+
2654+
interface I<out T> { }
2655+
2656+
class C { }
2657+
2658+
abstract class Problem<U>
2659+
{
2660+
protected abstract void M<T>(I<T?> i) where T : U;
2661+
}
2662+
2663+
class [|Bad|] : Problem<string>
2664+
{
2665+
}
2666+
""",
2667+
"""
2668+
#nullable enable
2669+
using System;
2670+
2671+
interface I<out T> { }
2672+
2673+
class C { }
2674+
2675+
abstract class Problem<U>
2676+
{
2677+
protected abstract void M<T>(I<T?> i) where T : U;
2678+
}
2679+
2680+
class Bad : Problem<string>
2681+
{
2682+
protected override void M<T>(I<T?> i) where T : class
2683+
{
2684+
throw new NotImplementedException();
2685+
}
2686+
}
2687+
""");
2688+
}
2689+
2690+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71225")]
2691+
public async Task TestConstrainedTypeParameter4()
2692+
{
2693+
await TestAllOptionsOffAsync(
2694+
"""
2695+
#nullable enable
2696+
using System;
2697+
2698+
interface I<out T> { }
2699+
2700+
class C { }
2701+
2702+
abstract class Problem<U>
2703+
{
2704+
protected abstract void M<T>(I<T?> i) where T : U;
2705+
}
2706+
2707+
class [|Bad|] : Problem<int[]>
2708+
{
2709+
}
2710+
""",
2711+
"""
2712+
#nullable enable
2713+
using System;
2714+
2715+
interface I<out T> { }
2716+
2717+
class C { }
2718+
2719+
abstract class Problem<U>
2720+
{
2721+
protected abstract void M<T>(I<T?> i) where T : U;
2722+
}
2723+
2724+
class Bad : Problem<int[]>
2725+
{
2726+
protected override void M<T>(I<T?> i) where T : class
2727+
{
2728+
throw new NotImplementedException();
2729+
}
2730+
}
2731+
""");
2732+
}
25562733
}

src/Analyzers/Core/CodeFixes/ImplementAbstractClass/AbstractImplementAbstractClassCodeFixProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
4949
context.RegisterCodeFix(
5050
CodeAction.Create(
5151
AnalyzersResources.Implement_abstract_class,
52-
c => data.ImplementAbstractClassAsync(throughMember: null, canDelegateAllMembers: null, c),
52+
cancellationToken => data.ImplementAbstractClassAsync(throughMember: null, canDelegateAllMembers: null, cancellationToken),
5353
id),
5454
context.Diagnostics);
5555

@@ -63,7 +63,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
6363
context.RegisterCodeFix(
6464
CodeAction.Create(
6565
string.Format(AnalyzersResources.Implement_through_0, through.Name),
66-
c => data.ImplementAbstractClassAsync(through, canDelegateAllMembers, c),
66+
cancellationToken => data.ImplementAbstractClassAsync(through, canDelegateAllMembers, cancellationToken),
6767
id),
6868
context.Diagnostics);
6969
}

src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/MethodGenerator.cs

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,12 +257,7 @@ private static SyntaxList<TypeParameterConstraintClauseSyntax> GenerateDefaultCo
257257
if (!referencedTypeParameters.Contains(typeParameter))
258258
continue;
259259

260-
var constraint = typeParameter switch
261-
{
262-
{ HasReferenceTypeConstraint: true } => s_classConstraint,
263-
{ HasValueTypeConstraint: true } => s_structConstraint,
264-
_ => s_defaultConstraint
265-
};
260+
var constraint = GetConstraint(typeParameter);
266261

267262
listOfClauses.Add(TypeParameterConstraintClause(
268263
typeParameter.Name.ToIdentifierName(),
@@ -272,6 +267,52 @@ private static SyntaxList<TypeParameterConstraintClauseSyntax> GenerateDefaultCo
272267
return [.. listOfClauses];
273268
}
274269

270+
private static TypeParameterConstraintSyntax GetConstraint(ITypeParameterSymbol typeParameter)
271+
{
272+
using var _ = PooledHashSet<ITypeParameterSymbol>.GetInstance(out var visited);
273+
var constraint = GetConstraintRecursive(typeParameter);
274+
275+
return constraint ?? s_defaultConstraint;
276+
277+
TypeParameterConstraintSyntax? GetConstraintRecursive(ITypeParameterSymbol typeParameter)
278+
{
279+
if (visited.Add(typeParameter))
280+
{
281+
// If it is explicitly marked as `T : struct` or `T : class` then we want to have the same constraint on the override.
282+
if (typeParameter.HasValueTypeConstraint)
283+
return s_structConstraint;
284+
285+
if (typeParameter.HasReferenceTypeConstraint)
286+
return s_classConstraint;
287+
288+
foreach (var constraintType in typeParameter.ConstraintTypes)
289+
{
290+
// If we ended up being constrained on a value type, then we have to have the `T : struct`
291+
// constraint to align with that.
292+
if (constraintType.IsValueType)
293+
return s_structConstraint;
294+
295+
// For all reference types *except* interfaces, we want the `T : class` constraint. An interface
296+
// can be implemented by a value type or a referernce type, so it adds no information to the
297+
// constraints.
298+
if (constraintType.IsReferenceType && constraintType.TypeKind != TypeKind.Interface)
299+
return s_classConstraint;
300+
301+
// If we have `where T : U` then peek into the other contraint to see if it adds information.
302+
if (constraintType is ITypeParameterSymbol constraintTypeParameter)
303+
{
304+
var constraint = GetConstraintRecursive(constraintTypeParameter);
305+
if (constraint != null)
306+
return constraint;
307+
}
308+
}
309+
}
310+
311+
// We learned nothing from this constraint.
312+
return null;
313+
}
314+
}
315+
275316
private static TypeParameterListSyntax? GenerateTypeParameterList(
276317
IMethodSymbol method, CSharpCodeGenerationContextInfo info)
277318
{

0 commit comments

Comments
 (0)