| File: IOperationExtensions.cs | Web Access |
| Project: src\runtime\src\tools\illink\src\ILLink.RoslynAnalyzer\ILLink.RoslynAnalyzer.csproj (ILLink.RoslynAnalyzer) |
// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Diagnostics; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; namespace ILLink.RoslynAnalyzer { // Copied from https://github.com/dotnet/roslyn/blob/9c6d864baca08d7572871701ab583cec18279426/src/Compilers/Core/Portable/Operations/OperationExtensions.cs internal static partial class IOperationExtensions { /// <summary> /// Returns the <see cref="ValueUsageInfo"/> for the given operation. /// This extension can be removed once https://github.com/dotnet/roslyn/issues/25057 is implemented. /// </summary> public static ValueUsageInfo GetValueUsageInfo(this IOperation operation, ISymbol containingSymbol) { /* | code | Read | Write | ReadableRef | WritableRef | NonReadWriteRef | | x.Prop = 1 | | ✔️ | | | | | x.Prop += 1 | ✔️ | ✔️ | | | | | x.Prop++ | ✔️ | ✔️ | | | | | Foo(x.Prop) | ✔️ | | | | | | Foo(x.Prop), | | | ✔️ | | | where void Foo(in T v) | Foo(out x.Prop) | | | | ✔️ | | | Foo(ref x.Prop) | | | ✔️ | ✔️ | | | nameof(x) | | | | | ✔️ | ️ | sizeof(x) | | | | | ✔️ | ️ | typeof(x) | | | | | ✔️ | ️ | out var x | | ✔️ | | | | ️ | case X x: | | ✔️ | | | | ️ | obj is X x | | ✔️ | | | | | ref var x = | | | ✔️ | ✔️ | | | ref readonly var x = | | | ✔️ | | | */ if (operation is ILocalReferenceOperation localReference && localReference.IsDeclaration && !localReference.IsImplicit) // Workaround for https://github.com/dotnet/roslyn/issues/30753 { // Declaration expression is a definition (write) for the declared local. return ValueUsageInfo.Write; } else if (operation is IDeclarationPatternOperation) { while (operation.Parent is IBinaryPatternOperation || operation.Parent is INegatedPatternOperation || operation.Parent is IRelationalPatternOperation) { operation = operation.Parent; } switch (operation.Parent) { case IPatternCaseClauseOperation: // A declaration pattern within a pattern case clause is a // write for the declared local. // For example, 'x' is defined and assigned the value from 'obj' below: // switch (obj) // { // case X x: // return ValueUsageInfo.Write; case IRecursivePatternOperation: // A declaration pattern within a recursive pattern is a // write for the declared local. // For example, 'x' is defined and assigned the value from 'obj' below: // (obj) switch // { // (X x) => ... // }; // return ValueUsageInfo.Write; case ISwitchExpressionArmOperation: // A declaration pattern within a switch expression arm is a // write for the declared local. // For example, 'x' is defined and assigned the value from 'obj' below: // obj switch // { // X x => ... // return ValueUsageInfo.Write; case IIsPatternOperation: // A declaration pattern within an is pattern is a // write for the declared local. // For example, 'x' is defined and assigned the value from 'obj' below: // if (obj is X x) // return ValueUsageInfo.Write; case IPropertySubpatternOperation: // A declaration pattern within a property sub-pattern is a // write for the declared local. // For example, 'x' is defined and assigned the value from 'obj.Property' below: // if (obj is { Property : int x }) // return ValueUsageInfo.Write; default: Debug.Fail("Unhandled declaration pattern context"); // Conservatively assume read/write. return ValueUsageInfo.ReadWrite; } } if (operation.Parent is IAssignmentOperation assignmentOperation && assignmentOperation.Target == operation) { return operation.Parent.IsAnyCompoundAssignment() ? ValueUsageInfo.ReadWrite : ValueUsageInfo.Write; } else if (operation.Parent is IIncrementOrDecrementOperation) { return ValueUsageInfo.ReadWrite; } else if (operation.Parent is IParenthesizedOperation parenthesizedOperation) { // Note: IParenthesizedOperation is specific to VB, where the parens cause a copy, so this cannot be classified as a write. Debug.Assert(parenthesizedOperation.Language == LanguageNames.VisualBasic); return parenthesizedOperation.GetValueUsageInfo(containingSymbol) & ~(ValueUsageInfo.Write | ValueUsageInfo.Reference); } else if (operation.Parent is INameOfOperation || operation.Parent is ITypeOfOperation || operation.Parent is ISizeOfOperation) { return ValueUsageInfo.Name; } else if (operation.Parent is IArgumentOperation argumentOperation) { switch (argumentOperation.Parameter?.RefKind) { case RefKind.RefReadOnly: return ValueUsageInfo.ReadableReference; case RefKind.Out: return ValueUsageInfo.WritableReference; case RefKind.Ref: return ValueUsageInfo.ReadableWritableReference; default: return ValueUsageInfo.Read; } } else if (operation.Parent is IReturnOperation returnOperation) { return returnOperation.GetRefKind(containingSymbol) switch { RefKind.RefReadOnly => ValueUsageInfo.ReadableReference, RefKind.Ref => ValueUsageInfo.ReadableWritableReference, _ => ValueUsageInfo.Read, }; } else if (operation.Parent is IConditionalOperation conditionalOperation) { if (operation == conditionalOperation.WhenTrue || operation == conditionalOperation.WhenFalse) { return GetValueUsageInfo(conditionalOperation, containingSymbol); } else { return ValueUsageInfo.Read; } } else if (operation.Parent is IReDimClauseOperation reDimClauseOperation && reDimClauseOperation.Operand == operation) { return (reDimClauseOperation.Parent as IReDimOperation)?.Preserve == true ? ValueUsageInfo.ReadWrite : ValueUsageInfo.Write; } else if (operation.Parent is IDeclarationExpressionOperation declarationExpression) { return declarationExpression.GetValueUsageInfo(containingSymbol); } else if (operation.IsInLeftOfDeconstructionAssignment(out _)) { return ValueUsageInfo.Write; } else if (operation.Parent is IVariableInitializerOperation variableInitializerOperation) { if (variableInitializerOperation.Parent is IVariableDeclaratorOperation variableDeclaratorOperation) { switch (variableDeclaratorOperation.Symbol.RefKind) { case RefKind.Ref: return ValueUsageInfo.ReadableWritableReference; case RefKind.RefReadOnly: return ValueUsageInfo.ReadableReference; } } } return ValueUsageInfo.Read; } public static RefKind GetRefKind(this IReturnOperation operation, ISymbol containingSymbol) { var containingMethod = TryGetContainingAnonymousFunctionOrLocalFunction(operation) ?? (containingSymbol as IMethodSymbol); return containingMethod?.RefKind ?? RefKind.None; } public static IMethodSymbol? TryGetContainingAnonymousFunctionOrLocalFunction(this IOperation? operation) { operation = operation?.Parent; while (operation != null) { switch (operation.Kind) { case OperationKind.AnonymousFunction: return ((IAnonymousFunctionOperation)operation).Symbol; case OperationKind.LocalFunction: return ((ILocalFunctionOperation)operation).Symbol; } operation = operation.Parent; } return null; } public static bool IsInLeftOfDeconstructionAssignment(this IOperation operation, out IDeconstructionAssignmentOperation? deconstructionAssignment) { deconstructionAssignment = null; var previousOperation = operation; var current = operation.Parent; while (current != null) { switch (current.Kind) { case OperationKind.DeconstructionAssignment: deconstructionAssignment = (IDeconstructionAssignmentOperation)current; return deconstructionAssignment.Target == previousOperation; case OperationKind.Tuple: case OperationKind.Conversion: case OperationKind.Parenthesized: previousOperation = current; current = current.Parent; continue; default: return false; } } return false; } /// <summary> /// Retursn true if the given operation is a regular compound assignment, /// i.e. <see cref="ICompoundAssignmentOperation"/> such as <code>a += b</code>, /// or a special null coalescing compoud assignment, i.e. <see cref="ICoalesceAssignmentOperation"/> /// such as <code>a ??= b</code>. /// </summary> public static bool IsAnyCompoundAssignment(this IOperation operation) { switch (operation) { case ICompoundAssignmentOperation: case ICoalesceAssignmentOperation: return true; default: return false; } } /// <summary> /// Finds the symbol of the caller to the current operation, helps to find out the symbol in cases where the operation passes /// through a lambda or a local function. /// </summary> /// <param name="operation">The operation to find the symbol for.</param> /// <param name="owningSymbol">The owning symbol of the entire operation context.</param> /// <returns>The symbol of the caller to the operation</returns> public static ISymbol FindContainingSymbol(this IOperation operation, ISymbol owningSymbol) { var parent = operation.Parent; while (parent is not null) { switch (parent) { case IAnonymousFunctionOperation lambda: return lambda.Symbol; case ILocalFunctionOperation local: return local.Symbol; case IMethodBodyBaseOperation: case IPropertyReferenceOperation: case IFieldReferenceOperation: case IEventReferenceOperation: return owningSymbol; default: parent = parent.Parent; break; } } return owningSymbol; } } }