Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12363,4 +12363,34 @@ private static string GetS(int y)

await TestExtractMethodAsync(code, expected);
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73044")]
public async Task CapturedPrimaryConstructorParameter()
{
var code = """"
public class Test(int value)
{
public int M()
{
return [|value + 1|];
}
}
"""";
var expected = """"
public class Test(int value)
{
public int M()
{
return NewMethod();
}

private int NewMethod()
{
return value + 1;
}
}
"""";

await TestExtractMethodAsync(code, expected);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,31 +140,40 @@ public AnalyzerResult Analyze()

// gather initial local or parameter variable info
GenerateVariableInfoMap(
bestEffort: false, model, dataFlowAnalysisData, symbolMap,
out var variableInfoMap, out var failedVariables);
bestEffort: false, model, dataFlowAnalysisData, symbolMap, out var variableInfoMap, out var failedVariables);
if (failedVariables.Count > 0)
{
// If we weren't able to figure something out, go back and regenerate the map
// this time in 'best effort' mode. We'll give the user a message saying there
// was a problem, but we allow them to proceed so they're not unnecessarily
// blocked just because we didn't understand something.
GenerateVariableInfoMap(
bestEffort: true, model, dataFlowAnalysisData, symbolMap,
out variableInfoMap, out var unused);
bestEffort: true, model, dataFlowAnalysisData, symbolMap, out variableInfoMap, out var unused);
Contract.ThrowIfFalse(unused.Count == 0);
}

var thisParameterBeingRead = (IParameterSymbol?)dataFlowAnalysisData.ReadInside.FirstOrDefault(IsThisParameter);
var isThisParameterWritten = dataFlowAnalysisData.WrittenInside.Any(static s => IsThisParameter(s));

// Need to generate an instance method if any primary constructor parameter is read or written inside the selection.
var primaryConstructorParameterReadOrWritten = dataFlowAnalysisData.ReadInside
.Concat(dataFlowAnalysisData.WrittenInside)
.OfType<IParameterSymbol>()
.FirstOrDefault(s => s.IsPrimaryConstructor(this.CancellationToken)) != null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FirstOrDefault

.Any ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now i want to write an analyzer/fixer that finds this pattern and suggests changing it.


var localFunctionCallsNotWithinSpan = symbolMap.Keys.Where(s => s.IsLocalFunction() && !s.Locations.Any(static (l, self) => self.SelectionResult.FinalSpan.Contains(l.SourceSpan), this));

// Checks to see if selection includes a local function call + if the given local function declaration is not included in the selection.
var containsAnyLocalFunctionCallNotWithinSpan = localFunctionCallsNotWithinSpan.Any();

// Checks to see if selection includes a non-static local function call + if the given local function declaration is not included in the selection.
var containsNonStaticLocalFunctionCallNotWithinSpan = containsAnyLocalFunctionCallNotWithinSpan && localFunctionCallsNotWithinSpan.Where(s => !s.IsStatic).Any();

var instanceMemberIsUsed = thisParameterBeingRead != null || isThisParameterWritten || containsNonStaticLocalFunctionCallNotWithinSpan;
var instanceMemberIsUsed = thisParameterBeingRead != null
|| isThisParameterWritten
|| containsNonStaticLocalFunctionCallNotWithinSpan
|| primaryConstructorParameterReadOrWritten;

var shouldBeReadOnly = !isThisParameterWritten
&& thisParameterBeingRead != null
&& thisParameterBeingRead.Type is { TypeKind: TypeKind.Struct, IsReadOnly: false };
Expand Down Expand Up @@ -480,11 +489,16 @@ private void GenerateVariableInfoMap(

foreach (var symbol in candidates)
{
if (symbol.IsThisParameter() ||
IsInteractiveSynthesizedParameter(symbol))
{
// We don't care about the 'this' parameter. It will be available to an extracted method already.
if (symbol.IsThisParameter())
continue;

// Primary constructor parameters will be in scope for any extracted method. No need to do anything special with them.
if (symbol is IParameterSymbol parameter && parameter.IsPrimaryConstructor(this.CancellationToken))
continue;

if (IsInteractiveSynthesizedParameter(symbol))
continue;
}

var captured = capturedMap.Contains(symbol);
var dataFlowIn = dataFlowInMap.Contains(symbol);
Expand Down
Loading