提交 eb9cf11f 编写于 作者: C Cyrus Najmabadi

Require System.Index/Range to be present to offer to use the new language features.

上级 df15c64a
......@@ -19,7 +19,7 @@ private class InfoCache
/// The System.Index type. Needed so that we only fixup code if we see the type
/// we're using has an indexer that takes an Index.
/// </summary>
private readonly INamedTypeSymbol _indexType;
public readonly INamedTypeSymbol IndexType;
/// <summary>
/// Mapping from a method like 'MyType.Get(int)' to the Length/Count property for
......@@ -29,7 +29,7 @@ private class InfoCache
public InfoCache(Compilation compilation)
{
_indexType = compilation.GetTypeByMetadataName("System.Index");
IndexType = compilation.GetTypeByMetadataName("System.Index");
_methodToMemberInfo = new ConcurrentDictionary<IMethodSymbol, MemberInfo>();
......@@ -85,7 +85,7 @@ private MemberInfo ComputeMemberInfo(IMethodSymbol method, bool requireIndexMemb
// this is the getter for an indexer. i.e. the user is calling something
// like s[...]. We need to see if there's an indexer that takes a System.Index
// value.
var indexer = GetIndexer(containingType, _indexType, method.ReturnType);
var indexer = GetIndexer(containingType, IndexType, method.ReturnType);
if (indexer != null)
{
// Type had a matching indexer. We can convert calls to the int-indexer to
......@@ -98,7 +98,7 @@ private MemberInfo ComputeMemberInfo(IMethodSymbol method, bool requireIndexMemb
Debug.Assert(method.MethodKind == MethodKind.Ordinary);
// it's a method like: `SomeType MyType.Get(int index)`. Look
// for an overload like: `SomeType MyType.Get(Range)`
var overloadedIndexMethod = GetOverload(method, _indexType);
var overloadedIndexMethod = GetOverload(method, IndexType);
if (overloadedIndexMethod != null)
{
return new MemberInfo(lengthLikeProperty, overloadedIndexMethod);
......
......@@ -58,6 +58,12 @@ protected override void InitializeWorker(AnalysisContext context)
var compilation = startContext.Compilation;
var infoCache = new InfoCache(compilation);
// The System.Index type is always required to offer this fix.
if (infoCache.IndexType == null)
{
return;
}
// Register to hear property references, so we can hear about calls to indexers
// like: s[s.Length - n]
context.RegisterOperationAction(
......
......@@ -64,8 +64,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
InvocationExpressionSyntax currentInvocation,
CancellationToken cancellationToken)
{
var invocation = semanticModel.GetOperation(currentInvocation, cancellationToken) as IInvocationOperation;
if (invocation != null)
if (semanticModel.GetOperation(currentInvocation, cancellationToken) is IInvocationOperation invocation)
{
var infoCache = new InfoCache(semanticModel.Compilation);
var resultOpt = AnalyzeInvocation(
......@@ -74,7 +73,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
if (resultOpt != null)
{
var result = resultOpt.Value;
var updatedNode = FixOne(semanticModel, result, cancellationToken);
var updatedNode = FixOne(result, cancellationToken);
if (updatedNode != null)
{
return currentRoot.ReplaceNode(result.Invocation, updatedNode);
......@@ -88,15 +87,14 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
private static InvocationExpressionSyntax GetInvocationExpression(Diagnostic d, CancellationToken cancellationToken)
=> (InvocationExpressionSyntax)d.AdditionalLocations[0].FindNode(getInnermostNodeForTie: true, cancellationToken);
private ExpressionSyntax FixOne(
SemanticModel semanticModel, Result result, CancellationToken cancellationToken)
private ExpressionSyntax FixOne(Result result, CancellationToken cancellationToken)
{
var invocation = result.Invocation;
var expression = invocation.Expression is MemberAccessExpressionSyntax memberAccess
? memberAccess.Expression
: invocation.Expression;
var rangeExpression = CreateRangeExpression(semanticModel, result, cancellationToken);
var rangeExpression = CreateRangeExpression(result);
var argument = Argument(rangeExpression).WithAdditionalAnnotations(Formatter.Annotation);
var arguments = SingletonSeparatedList(argument);
......@@ -118,22 +116,15 @@ private static InvocationExpressionSyntax GetInvocationExpression(Diagnostic d,
}
}
private RangeExpressionSyntax CreateRangeExpression(
SemanticModel semanticModel, Result result, CancellationToken cancellationToken)
{
switch (result.Kind)
private RangeExpressionSyntax CreateRangeExpression(Result result)
=> result.Kind switch
{
case ResultKind.Computed:
return CreateComputedRange(semanticModel, result, cancellationToken);
case ResultKind.Constant:
return CreateConstantRange(semanticModel, result, cancellationToken);
default:
throw ExceptionUtilities.Unreachable;
}
}
ResultKind.Computed => CreateComputedRange(result),
ResultKind.Constant => CreateConstantRange(result),
_ => throw ExceptionUtilities.Unreachable,
};
private RangeExpressionSyntax CreateComputedRange(
SemanticModel semanticModel, Result result, CancellationToken cancellationToken)
private RangeExpressionSyntax CreateComputedRange(Result result)
{
// We have enough information now to generate `start..end`. However, this will often
// not be what the user wants. For example, generating `start..expr.Length` is not as
......@@ -143,24 +134,18 @@ private static InvocationExpressionSyntax GetInvocationExpression(Diagnostic d,
var startOperation = result.Op1;
var endOperation = result.Op2;
var startExpr = (ExpressionSyntax)startOperation.Syntax;
var endExpr = (ExpressionSyntax)endOperation.Syntax;
var startFromEnd = false;
var endFromEnd = false;
var lengthLikeProperty = result.MemberInfo.LengthLikeProperty;
var instance = result.InvocationOperation.Instance;
// If our start-op is actually equivalent to `expr.Length - val`, then just change our
// start-op to be `val` and record that we should emit it as `^val`.
startFromEnd = IsFromEnd(lengthLikeProperty, instance, ref startOperation);
startExpr = (ExpressionSyntax)startOperation.Syntax;
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`.
endFromEnd = IsFromEnd(lengthLikeProperty, instance, ref endOperation);
endExpr = (ExpressionSyntax)endOperation.Syntax;
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..`
......@@ -182,16 +167,9 @@ private static InvocationExpressionSyntax GetInvocationExpression(Diagnostic d,
endExpr != null && endFromEnd ? IndexExpression(endExpr) : endExpr);
}
private static ExpressionSyntax GetExpression(ImmutableDictionary<string, string> props, ExpressionSyntax expr, string omitKey, string fromEndKey)
=> props.ContainsKey(omitKey)
? null
: props.ContainsKey(fromEndKey) ? IndexExpression(expr) : expr;
private static RangeExpressionSyntax CreateConstantRange(
SemanticModel semanticModel, Result result, CancellationToken cancellationToken)
private static RangeExpressionSyntax CreateConstantRange(Result result)
{
var constant1Syntax = (ExpressionSyntax)result.Op1.Syntax;
var constant2Syntax = (ExpressionSyntax)result.Op2.Syntax;
// the form is s.Slice(constant1, s.Length - constant2). Want to generate
// s[constant1..(constant2-constant1)]
......
......@@ -20,12 +20,12 @@ public class InfoCache
/// The System.Range type. Needed so that we only fixup code if we see the type
/// we're using has an indexer that takes a Range.
/// </summary>
private readonly INamedTypeSymbol _rangeType;
public readonly INamedTypeSymbol RangeType;
private readonly ConcurrentDictionary<IMethodSymbol, MemberInfo> _methodToMemberInfo;
public InfoCache(Compilation compilation)
{
_rangeType = compilation.GetTypeByMetadataName("System.Range");
RangeType = compilation.GetTypeByMetadataName("System.Range");
_methodToMemberInfo = new ConcurrentDictionary<IMethodSymbol, MemberInfo>();
......@@ -89,7 +89,7 @@ private MemberInfo ComputeMemberInfo(IMethodSymbol sliceLikeMethod, bool require
// it's a method like: MyType MyType.Slice(int start, int length). Look for an
// indexer like `MyType MyType.this[Range range]`. If we can't find one return
// 'default' so we'll consider this named-type non-viable.
var indexer = GetIndexer(containingType, _rangeType, containingType);
var indexer = GetIndexer(containingType, RangeType, containingType);
if (indexer != null)
{
return new MemberInfo(lengthLikeProperty, overloadedMethodOpt: null);
......@@ -98,7 +98,7 @@ private MemberInfo ComputeMemberInfo(IMethodSymbol sliceLikeMethod, bool require
// it's a method like: `SomeType MyType.Slice(int start, int length)`. Look
// for an overload like: `SomeType MyType.Slice(Range)`
var overloadedRangeMethod = GetOverload(sliceLikeMethod, _rangeType);
var overloadedRangeMethod = GetOverload(sliceLikeMethod, RangeType);
if (overloadedRangeMethod != null)
{
return new MemberInfo(lengthLikeProperty, overloadedRangeMethod);
......
......@@ -52,9 +52,14 @@ protected override void InitializeWorker(AnalysisContext context)
// We're going to be checking every invocation in the compilation. Cache information
// we compute in this object so we don't have to continually recompute it.
var infoCache = new InfoCache(compilationContext.Compilation);
compilationContext.RegisterOperationAction(
c => AnalyzeInvocation(c, infoCache),
OperationKind.Invocation);
// The System.Range type is always required to offer this fix.
if (infoCache.RangeType != null)
{
compilationContext.RegisterOperationAction(
c => AnalyzeInvocation(c, infoCache),
OperationKind.Invocation);
}
});
}
......
......@@ -67,9 +67,6 @@ public static bool IsSubtraction(IOperation operation, out IBinaryOperation subt
return false;
}
private static bool IsConstantInt32(IOperation operation)
=> operation.ConstantValue.HasValue && operation.ConstantValue.Value is int;
/// <summary>
/// Look for methods like "SomeType MyType.Get(int)". Also matches against the 'getter'
/// of an indexer like 'SomeType MyType.this[int]`
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册