File: IOperationExtensions.cs
Web Access
Project: src\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;
		}
	}
}