提交 71360a43 编写于 作者: A Alexander Luzgarev

Support basic cases in UseRangeOperator

上级 31381123
......@@ -69,6 +69,42 @@ public bool TryGetMemberInfo(IMethodSymbol method, out MemberInfo memberInfo)
return memberInfo.LengthLikeProperty != null;
}
public bool TryGetMemberInfoOneArgument(IMethodSymbol method, out MemberInfo memberInfo)
{
if (!IsSliceLikeMethodWithOneArgument(method))
{
memberInfo = default;
return false;
}
if (_methodToMemberInfo.TryGetValue(method, out memberInfo))
{
return memberInfo.LengthLikeProperty != null;
}
// Find overload of our method that is a slice-like method with two arguments.
// Computing member info for this method will also check that the containing type
// has an int32 'Length' or 'Count' property, and has a suitable indexer,
// so we don't have to.
var overloadWithTwoArguments = method.ContainingType
.GetMembers(method.Name)
.OfType<IMethodSymbol>()
.FirstOrDefault(s => IsSliceLikeMethod(s));
if (overloadWithTwoArguments is null)
{
memberInfo = default;
return false;
}
// Since the search is expensive, we keep both the original one-argument and
// two-arguments overload as keys in the cache, pointing to the same
// member information object.
var newMemberInfo = _methodToMemberInfo.GetOrAdd(overloadWithTwoArguments, _ => ComputeMemberInfo(overloadWithTwoArguments, requireRangeMember: true));
_methodToMemberInfo.GetOrAdd(method, _ => newMemberInfo);
memberInfo = newMemberInfo;
return memberInfo.LengthLikeProperty != null;
}
private MemberInfo ComputeMemberInfo(IMethodSymbol sliceLikeMethod, bool requireRangeMember)
{
Debug.Assert(IsSliceLikeMethod(sliceLikeMethod));
......
......@@ -12,7 +12,7 @@ internal partial class CSharpUseRangeOperatorDiagnosticAnalyzer
{
public enum ResultKind
{
// like s.Substring(expr, s.Length - expr). 'expr' has to match on both sides.
// like s.Substring(expr, s.Length - expr) or s.Substring(expr). 'expr' has to match on both sides.
Computed,
// like s.Substring(constant1, s.Length - constant2). the constants don't have to match.
......@@ -28,6 +28,10 @@ public enum ResultKind
public readonly IMethodSymbol SliceLikeMethod;
public readonly MemberInfo MemberInfo;
public readonly IOperation Op1;
/// <summary>
/// Can be null, if we are dealing with one-argument call to a slice-like method.
/// </summary>
public readonly IOperation Op2;
public Result(
......
......@@ -111,13 +111,54 @@ protected override void InitializeWorker(AnalysisContext context)
}
}
// look for `s.Slice(e1, end - e2)`
if (invocation.Instance is null ||
invocation.Arguments.Length != 2)
// look for `s.Slice(e1, end - e2)` or `s.Slice(e1)`
if (invocation.Instance is null)
{
return null;
}
return invocation.Arguments.Length switch
{
1 => AnalyzeInvocationWithOneArgument(invocation, infoCache, invocationSyntax, option),
2 => AnalyzeInvocationWithTwoArguments(invocation, infoCache, invocationSyntax, option),
_ => null,
};
}
private static Result? AnalyzeInvocationWithOneArgument(
IInvocationOperation invocation,
InfoCache infoCache,
InvocationExpressionSyntax invocationSyntax,
CodeStyleOption2<bool> option)
{
// See if the call is to something slice-like.
var targetMethod = invocation.TargetMethod;
// Try to see if we're calling into some sort of Slice method with a matching
// indexer or overload
if (!infoCache.TryGetMemberInfoOneArgument(targetMethod, out var memberInfo))
{
return null;
}
var startOperation = invocation.Arguments[0].Value;
return new Result(
ResultKind.Computed,
option,
invocation,
invocationSyntax,
targetMethod,
memberInfo,
startOperation,
null); // secondOperation is null: the range will run to the end.
}
private static Result? AnalyzeInvocationWithTwoArguments(
IInvocationOperation invocation,
InfoCache infoCache,
InvocationExpressionSyntax invocationSyntax,
CodeStyleOption2<bool> option)
{
// See if the call is to something slice-like.
var targetMethod = invocation.TargetMethod;
......
......@@ -91,6 +91,21 @@ public static bool IsSliceLikeMethod(IMethodSymbol method)
IsSliceFirstParameter(method.OriginalDefinition.Parameters[0]) &&
IsSliceSecondParameter(method.OriginalDefinition.Parameters[1]);
/// <summary>
/// Look for methods like "SomeType MyType.Slice(int start)". Note that the
/// name of the parameter is checked to ensure it is appropriate slice-like.
/// This name was picked by examining the patterns in the BCL for slicing members.
/// </summary>
public static bool IsSliceLikeMethodWithOneArgument(IMethodSymbol method)
=> method != null &&
IsPublicInstance(method) &&
method.Parameters.Length == 1 &&
// From: https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/ranges.md#decisions-made-during-implementation
//
// When looking for the pattern members, we look for original definitions, not
// constructed members
IsSliceFirstParameter(method.OriginalDefinition.Parameters[0]);
private static bool IsSliceFirstParameter(IParameterSymbol parameter)
=> parameter.Type.SpecialType == SpecialType.System_Int32 &&
(parameter.Name == "start" || parameter.Name == "startIndex");
......
......@@ -150,15 +150,28 @@ private static RangeExpressionSyntax CreateComputedRange(Result result)
var startFromEnd = IsFromEnd(lengthLikeProperty, instance, ref startOperation);
var startExpr = (ExpressionSyntax)startOperation.Syntax;
// Similarly, if our end-op is actually equivalent to `expr.Length - val`, then just
// change our end-op to be `val` and record that we should emit it as `^val`.
var endFromEnd = IsFromEnd(lengthLikeProperty, instance, ref endOperation);
var endExpr = (ExpressionSyntax)endOperation.Syntax;
// If the range operation goes to 'expr.Length' then we can just leave off the end part
// of the range. i.e. `start..`
if (IsInstanceLengthCheck(lengthLikeProperty, instance, endOperation))
var endFromEnd = false;
ExpressionSyntax endExpr;
if (!(endOperation is null))
{
// We need to do the same for the second argument, since it's present.
// Similarly, if our end-op is actually equivalent to `expr.Length - val`, then just
// change our end-op to be `val` and record that we should emit it as `^val`.
endFromEnd = IsFromEnd(lengthLikeProperty, instance, ref endOperation);
endExpr = (ExpressionSyntax)endOperation.Syntax;
// If the range operation goes to 'expr.Length' then we can just leave off the end part
// of the range. i.e. `start..`
if (IsInstanceLengthCheck(lengthLikeProperty, instance, endOperation))
{
endExpr = null;
}
}
else
{
// We are dealing with one-argument case, so we should leave off
// the end part of the range: `start..`.
endExpr = null;
}
......
......@@ -204,6 +204,96 @@ void Goo(string s, int bar, int baz)
}.RunAsync();
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestSubstringOneArgument()
{
var source =
@"
class C
{
void Goo(string s)
{
var v = s.Substring([|1|]);
}
}";
var fixedSource =
@"
class C
{
void Goo(string s)
{
var v = s[1..];
}
}";
await new VerifyCS.Test
{
ReferenceAssemblies = ReferenceAssemblies.NetCore.NetCoreApp31,
TestCode = source,
FixedCode = fixedSource,
}.RunAsync();
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestSliceOneArgument()
{
var source =
@"
class C
{
void Goo(Span<int> s)
{
var v = s.Slice([|1|]);
}
}";
var fixedSource =
@"
class C
{
void Goo(Span<int> s)
{
var v = s[1..];
}
}";
await new VerifyCS.Test
{
ReferenceAssemblies = ReferenceAssemblies.NetCore.NetCoreApp31,
TestCode = source,
FixedCode = fixedSource,
}.RunAsync();
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestExpressionOneArgument()
{
var source =
@"
class C
{
void Goo(string s, int bar)
{
var v = s.Substring([|bar|]);
}
}";
var fixedSource =
@"
class C
{
void Goo(string s, int bar)
{
var v = s[bar..];
}
}";
await new VerifyCS.Test
{
ReferenceAssemblies = ReferenceAssemblies.NetCore.NetCoreApp31,
TestCode = source,
FixedCode = fixedSource,
}.RunAsync();
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestConstantSubtraction1()
{
......@@ -416,6 +506,38 @@ void Goo(string s, string t)
}.RunAsync();
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestFixAllInvocationToElementAccess2()
{
// Note: once the IOp tree has support for range operators, this should
// simplify even further.
var source =
@"
class C
{
void Goo(string s, string t)
{
var v = t.Substring([|s.Substring([|1|])[0]|]);
}
}";
var fixedSource =
@"
class C
{
void Goo(string s, string t)
{
var v = t[s[1..][0]..];
}
}";
await new VerifyCS.Test
{
ReferenceAssemblies = ReferenceAssemblies.NetCore.NetCoreApp31,
TestCode = source,
FixedCode = fixedSource,
}.RunAsync();
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestWithTypeWithActualSliceMethod1()
{
......@@ -479,5 +601,37 @@ void Goo(Span<int> s)
FixedCode = fixedSource,
}.RunAsync();
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseRangeOperator)]
public async Task TestWithTypeWithActualSliceMethod3()
{
var source =
@"
using System;
class C
{
void Goo(Span<int> s)
{
var v = s.Slice([|1|]);
}
}";
var fixedSource =
@"
using System;
class C
{
void Goo(Span<int> s)
{
var v = s[1..];
}
}";
await new VerifyCS.Test
{
ReferenceAssemblies = ReferenceAssemblies.NetCore.NetCoreApp31,
TestCode = source,
FixedCode = fixedSource,
}.RunAsync();
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册