Skip to content

Commit

Permalink
Generate delegating constructor should make the implicit no-arg const…
Browse files Browse the repository at this point in the history
…ructor explicit to preserve binary and source compat (#76330)
  • Loading branch information
CyrusNajmabadi authored Dec 9, 2024
2 parents 1bf248f + 748e302 commit f8bc4f5
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1620,5 +1620,92 @@ public Program()
""");
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51049")]
public async Task TestGenerateDefaultConstructorPreserveBinaryCompat1()
{
await TestRefactoringAsync(
"""
class Base
{
protected Base()
{
}
protected Base(int i)
{
}
}
sealed class Program : [||]Base
{
}
""",
"""
class Base
{
protected Base()
{
}
protected Base(int i)
{
}
}
sealed class Program : Base
{
public Program()
{
}
}
""");
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51049")]
public async Task TestGenerateDefaultConstructorPreserveBinaryCompat2()
{
await TestRefactoringAsync(
"""
class Base
{
protected Base()
{
}
protected Base(int i)
{
}
}
sealed class Program : [||]Base
{
}
""",
"""
class Base
{
protected Base()
{
}
protected Base(int i)
{
}
}
sealed class Program : Base
{
public Program()
{
}
public Program(int i) : base(i)
{
}
}
""",
index: 1);
}

#endif
}
4 changes: 1 addition & 3 deletions src/Analyzers/Core/CodeFixes/CodeFixes.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@
<Compile Include="$(MSBuildThisFileDirectory)GenerateConstructor\GenerateConstructorHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GenerateConstructor\IGenerateConstructorService.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GenerateDefaultConstructors\AbstractGenerateDefaultConstructorCodeFixProvider.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GenerateDefaultConstructors\AbstractGenerateDefaultConstructorsService.AbstractCodeAction.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GenerateDefaultConstructors\AbstractGenerateDefaultConstructorsService.CodeAction.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GenerateDefaultConstructors\AbstractGenerateDefaultConstructorsService.CodeActionAll.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GenerateDefaultConstructors\GenerateDefaultConstructorsCodeAction.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GenerateDefaultConstructors\AbstractGenerateDefaultConstructorsService.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GenerateDefaultConstructors\AbstractGenerateDefaultConstructorsService.State.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GenerateDefaultConstructors\IGenerateDefaultConstructorsService.cs" />
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,9 @@ private bool TryInitialize(
var destinationProvider = semanticDocument.Project.Solution.Services.GetLanguageServices(ClassType.Language);
var isCaseSensitive = syntaxFacts.IsCaseSensitive;

UnimplementedConstructors =
baseType.InstanceConstructors
.WhereAsArray(c => c.IsAccessibleWithin(ClassType) &&
IsMissing(c, classConstructors, isCaseSensitive));
UnimplementedConstructors = baseType
.InstanceConstructors
.WhereAsArray(c => c.IsAccessibleWithin(ClassType) && IsMissing(c, classConstructors, isCaseSensitive));

return UnimplementedConstructors.Length > 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@

using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.GenerateDefaultConstructors;

Expand All @@ -37,11 +38,28 @@ public async Task<ImmutableArray<CodeAction>> GenerateDefaultConstructorsAsync(
var state = State.Generate((TService)this, semanticDocument, textSpan, forRefactoring, cancellationToken);
if (state != null)
{
// If the user only has an implicit no-arg constructor, and we're adding a constructor with args,
// then the compiler will not emit the implicit no-arg constructor anymore. So we need to include
// that member to ensure binary compat when generating members.
var unimplementedDefaultConstructor = state.UnimplementedConstructors.FirstOrDefault(
m => m.Parameters.Length == 0);

foreach (var constructor in state.UnimplementedConstructors)
result.Add(new GenerateDefaultConstructorCodeAction(document, state, constructor));
{
Contract.ThrowIfNull(state.ClassType);

result.Add(new GenerateDefaultConstructorsCodeAction(
document, state,
string.Format(CodeFixesResources.Generate_constructor_0_1,
state.ClassType.Name,
string.Join(", ", constructor.Parameters.Select(p => p.Name))),
unimplementedDefaultConstructor == null || unimplementedDefaultConstructor == constructor
? [constructor]
: [unimplementedDefaultConstructor, constructor]));
}

if (state.UnimplementedConstructors.Length > 1)
result.Add(new CodeActionAll(document, state, state.UnimplementedConstructors));
result.Add(new GenerateDefaultConstructorsCodeAction(document, state, CodeFixesResources.Generate_all, state.UnimplementedConstructors));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -22,26 +22,17 @@ namespace Microsoft.CodeAnalysis.GenerateDefaultConstructors;

internal abstract partial class AbstractGenerateDefaultConstructorsService<TService>
{
private abstract class AbstractCodeAction : CodeAction
private sealed class GenerateDefaultConstructorsCodeAction(
Document document,
State state,
string title,
ImmutableArray<IMethodSymbol> constructors) : CodeAction
{
private readonly IList<IMethodSymbol> _constructors;
private readonly Document _document;
private readonly State _state;
private readonly string _title;
private readonly ImmutableArray<IMethodSymbol> _constructors = constructors;
private readonly Document _document = document;
private readonly State _state = state;

protected AbstractCodeAction(
Document document,
State state,
IList<IMethodSymbol> constructors,
string title)
{
_document = document;
_state = state;
_constructors = constructors;
_title = title;
}

public override string Title => _title;
public override string Title => title;

protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken)
{
Expand Down

0 comments on commit f8bc4f5

Please sign in to comment.