|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Operations
{
internal sealed partial class CSharpOperationFactory
{
internal ImmutableArray<BoundStatement> ToStatements(BoundStatement? statement)
{
if (statement == null)
{
return ImmutableArray<BoundStatement>.Empty;
}
if (statement.Kind == BoundKind.StatementList)
{
return ((BoundStatementList)statement).Statements;
}
return ImmutableArray.Create(statement);
}
private IInstanceReferenceOperation CreateImplicitReceiver(SyntaxNode syntax, TypeSymbol type) =>
new InstanceReferenceOperation(InstanceReferenceKind.ImplicitReceiver, _semanticModel, syntax, type.GetPublicSymbol(), isImplicit: true);
internal IArgumentOperation CreateArgumentOperation(ArgumentKind kind, IParameterSymbol? parameter, BoundExpression expression)
{
// put argument syntax to argument operation
IOperation value = Create(expression is BoundConversion { IsParamsArrayOrCollection: true } conversion ? conversion.Operand : expression);
(SyntaxNode syntax, bool isImplicit) = expression.Syntax is { Parent: ArgumentSyntax or AttributeArgumentSyntax } ? (expression.Syntax.Parent, expression.WasCompilerGenerated) : (value.Syntax, true);
return new ArgumentOperation(
kind,
parameter,
value,
OperationFactory.IdentityConversion,
OperationFactory.IdentityConversion,
_semanticModel,
syntax,
isImplicit);
}
internal IVariableInitializerOperation? CreateVariableDeclaratorInitializer(BoundLocalDeclaration boundLocalDeclaration, SyntaxNode syntax)
{
if (boundLocalDeclaration.InitializerOpt != null)
{
SyntaxNode? initializerSyntax = null;
bool initializerIsImplicit = false;
if (syntax is VariableDeclaratorSyntax variableDeclarator)
{
initializerSyntax = variableDeclarator.Initializer;
}
else
{
Debug.Fail($"Unexpected syntax kind: {syntax.Kind()}");
}
if (initializerSyntax == null)
{
// There is no explicit syntax for the initializer, so we use the initializerValue's syntax and mark the operation as implicit.
initializerSyntax = boundLocalDeclaration.InitializerOpt.Syntax;
initializerIsImplicit = true;
}
IOperation value = Create(boundLocalDeclaration.InitializerOpt);
return new VariableInitializerOperation(locals: ImmutableArray<ILocalSymbol>.Empty, value, _semanticModel, initializerSyntax, initializerIsImplicit);
}
return null;
}
private IVariableDeclaratorOperation CreateVariableDeclaratorInternal(BoundLocalDeclaration boundLocalDeclaration, SyntaxNode syntax)
{
ILocalSymbol symbol = boundLocalDeclaration.LocalSymbol.GetPublicSymbol();
bool isImplicit = false;
IVariableInitializerOperation? initializer = CreateVariableDeclaratorInitializer(boundLocalDeclaration, syntax);
ImmutableArray<IOperation> ignoredDimensions = CreateFromArray<BoundExpression, IOperation>(boundLocalDeclaration.ArgumentsOpt);
return new VariableDeclaratorOperation(symbol, initializer, ignoredDimensions, _semanticModel, syntax, isImplicit);
}
[return: NotNullIfNotNull(nameof(boundLocal))]
internal IVariableDeclaratorOperation? CreateVariableDeclarator(BoundLocal? boundLocal)
{
return boundLocal == null ? null : new VariableDeclaratorOperation(boundLocal.LocalSymbol.GetPublicSymbol(), initializer: null, ignoredArguments: ImmutableArray<IOperation>.Empty, semanticModel: _semanticModel, syntax: boundLocal.Syntax, isImplicit: false);
}
internal IOperation? CreateReceiverOperation(BoundNode? instance, Symbol? symbol)
{
if (instance == null || instance.Kind == BoundKind.TypeExpression)
{
return null;
}
// Static members cannot have an implicit this receiver
if (symbol != null && symbol.IsStatic && instance.WasCompilerGenerated && instance.Kind == BoundKind.ThisReference)
{
return null;
}
return Create(instance);
}
private bool IsCallVirtual(MethodSymbol? targetMethod, BoundExpression? receiver)
{
return (object?)targetMethod != null && receiver != null &&
(targetMethod.IsVirtual || targetMethod.IsAbstract || targetMethod.IsOverride) &&
!receiver.SuppressVirtualCalls;
}
private bool IsMethodInvalid(LookupResultKind resultKind, MethodSymbol targetMethod) =>
resultKind == LookupResultKind.OverloadResolutionFailure || targetMethod?.OriginalDefinition is ErrorMethodSymbol;
internal IEventReferenceOperation CreateBoundEventAccessOperation(BoundEventAssignmentOperator boundEventAssignmentOperator)
{
SyntaxNode syntax = boundEventAssignmentOperator.Syntax;
// BoundEventAssignmentOperator doesn't hold on to BoundEventAccess provided during binding.
// Based on the implementation of those two bound node types, the following data can be retrieved w/o changing BoundEventAssignmentOperator:
// 1. the type of BoundEventAccess is the type of the event symbol.
// 2. the constant value of BoundEventAccess is always null.
// 3. the syntax of the boundEventAssignmentOperator is always AssignmentExpressionSyntax, so the syntax for the event reference would be the LHS of the assignment.
IEventSymbol @event = boundEventAssignmentOperator.Event.GetPublicSymbol();
IOperation? instance = CreateReceiverOperation(boundEventAssignmentOperator.ReceiverOpt, boundEventAssignmentOperator.Event);
SyntaxNode eventAccessSyntax = ((AssignmentExpressionSyntax)syntax).Left;
bool isImplicit = boundEventAssignmentOperator.WasCompilerGenerated;
TypeParameterSymbol? constrainedToType = GetConstrainedToType(boundEventAssignmentOperator.Event, boundEventAssignmentOperator.ReceiverOpt);
return new EventReferenceOperation(@event, constrainedToType.GetPublicSymbol(), instance, _semanticModel, eventAccessSyntax, @event.Type, isImplicit);
}
internal IOperation CreateDelegateTargetOperation(BoundNode delegateNode)
{
if (delegateNode is BoundConversion boundConversion)
{
if (boundConversion.ConversionKind == ConversionKind.MethodGroup)
{
// We don't check HasErrors on the conversion here because if we actually have a MethodGroup conversion,
// overload resolution succeeded. The resulting method could be invalid for other reasons, but we don't
// hide the resolved method.
Debug.Assert(boundConversion.SymbolOpt is not null);
return CreateBoundMethodGroupSingleMethodOperation((BoundMethodGroup)boundConversion.Operand,
boundConversion.SymbolOpt,
boundConversion.SuppressVirtualCalls);
}
else
{
return Create(boundConversion.Operand);
}
}
else
{
var boundDelegateCreationExpression = (BoundDelegateCreationExpression)delegateNode;
if (boundDelegateCreationExpression.Argument.Kind == BoundKind.MethodGroup &&
boundDelegateCreationExpression.MethodOpt != null)
{
// If this is a method binding, and a valid candidate method was found, then we want to expose
// this child as an IMethodBindingReference. Otherwise, we want to just delegate to the standard
// CSharpOperationFactory behavior. Note we don't check HasErrors here because if we have a method group,
// overload resolution succeeded, even if the resulting method isn't valid for some other reason.
BoundMethodGroup boundMethodGroup = (BoundMethodGroup)boundDelegateCreationExpression.Argument;
return CreateBoundMethodGroupSingleMethodOperation(boundMethodGroup, boundDelegateCreationExpression.MethodOpt, boundMethodGroup.SuppressVirtualCalls);
}
else
{
return Create(boundDelegateCreationExpression.Argument);
}
}
}
internal IOperation CreateMemberInitializerInitializedMember(BoundNode initializedMember)
{
switch (initializedMember)
{
case BoundObjectInitializerMember objectInitializer:
return CreateBoundObjectInitializerMemberOperation(objectInitializer, isObjectOrCollectionInitializer: true);
case BoundDynamicObjectInitializerMember dynamicInitializer:
return CreateBoundDynamicObjectInitializerMemberOperation(dynamicInitializer);
default:
return Create(initializedMember);
}
}
internal ImmutableArray<IArgumentOperation> DeriveArguments(BoundNode containingExpression)
{
switch (containingExpression.Kind)
{
case BoundKind.ObjectInitializerMember:
{
var boundObjectInitializerMember = (BoundObjectInitializerMember)containingExpression;
var property = (PropertySymbol?)boundObjectInitializerMember.MemberSymbol;
Debug.Assert(property is not null);
return DeriveArguments(
property,
boundObjectInitializerMember.Arguments,
boundObjectInitializerMember.ArgsToParamsOpt,
boundObjectInitializerMember.DefaultArguments);
}
case BoundKind.IndexerAccess:
{
var boundIndexer = (BoundIndexerAccess)containingExpression;
return DeriveArguments(boundIndexer.Indexer,
boundIndexer.Arguments,
boundIndexer.ArgsToParamsOpt,
boundIndexer.DefaultArguments);
}
case BoundKind.ObjectCreationExpression:
{
var objectCreation = (BoundObjectCreationExpression)containingExpression;
return DeriveArguments(objectCreation.Constructor,
objectCreation.Arguments,
objectCreation.ArgsToParamsOpt,
objectCreation.DefaultArguments);
}
case BoundKind.Attribute:
var attribute = (BoundAttribute)containingExpression;
Debug.Assert(attribute.Constructor is not null);
return DeriveArguments(attribute.Constructor,
attribute.ConstructorArguments,
attribute.ConstructorArgumentsToParamsOpt,
attribute.ConstructorDefaultArguments);
case BoundKind.Call:
{
var boundCall = (BoundCall)containingExpression;
return DeriveArguments(boundCall.Method,
boundCall.Arguments,
boundCall.ArgsToParamsOpt,
boundCall.DefaultArguments,
boundCall.InvokedAsExtensionMethod);
}
case BoundKind.CollectionElementInitializer:
{
var boundCollectionElementInitializer = (BoundCollectionElementInitializer)containingExpression;
return DeriveArguments(boundCollectionElementInitializer.AddMethod,
boundCollectionElementInitializer.Arguments,
boundCollectionElementInitializer.ArgsToParamsOpt,
boundCollectionElementInitializer.DefaultArguments,
boundCollectionElementInitializer.InvokedAsExtensionMethod);
}
case BoundKind.FunctionPointerInvocation:
{
var boundFunctionPointerInvocation = (BoundFunctionPointerInvocation)containingExpression;
return DeriveArguments(boundFunctionPointerInvocation.FunctionPointer.Signature,
boundFunctionPointerInvocation.Arguments,
default,
BitVector.Empty,
false);
}
default:
throw ExceptionUtilities.UnexpectedValue(containingExpression.Kind);
}
}
private ImmutableArray<IArgumentOperation> DeriveArguments(
Symbol methodOrIndexer,
ImmutableArray<BoundExpression> boundArguments,
ImmutableArray<int> argumentsToParametersOpt,
BitVector defaultArguments,
bool invokedAsExtensionMethod = false)
{
// We can simply return empty array only if both parameters and boundArguments are empty, because:
// - if only parameters is empty, there's error in code but we still need to return provided expression.
// - if boundArguments is empty, then either there's error or we need to provide values for optional/param-array parameters.
if (methodOrIndexer.GetParameters().IsDefaultOrEmpty && boundArguments.IsDefaultOrEmpty)
{
return ImmutableArray<IArgumentOperation>.Empty;
}
return MakeArgumentsInEvaluationOrder(
operationFactory: this,
arguments: boundArguments,
methodOrIndexer: methodOrIndexer,
argsToParamsOpt: argumentsToParametersOpt,
defaultArguments: defaultArguments,
invokedAsExtensionMethod: invokedAsExtensionMethod);
}
private static ImmutableArray<IArgumentOperation> MakeArgumentsInEvaluationOrder(
CSharpOperationFactory operationFactory,
ImmutableArray<BoundExpression> arguments,
Symbol methodOrIndexer,
ImmutableArray<int> argsToParamsOpt,
BitVector defaultArguments,
bool invokedAsExtensionMethod)
{
// We need to do a fancy rewrite under the following circumstances:
// (1) a params array is being used; we need to generate the array.
// (2) named arguments were provided out-of-order of the parameters.
//
// If neither of those are the case then we can just take an early out.
if (LocalRewriter.CanSkipRewriting(arguments, methodOrIndexer, argsToParamsOpt, invokedAsExtensionMethod, true, out _))
{
// In this case, there's no named argument provided.
// So we just return list of arguments as is.
ImmutableArray<ParameterSymbol> parameters = methodOrIndexer.GetParameters();
ArrayBuilder<IArgumentOperation> argumentsBuilder = ArrayBuilder<IArgumentOperation>.GetInstance(arguments.Length);
int i = 0;
for (; i < parameters.Length; ++i)
{
var argumentKind = GetArgumentKind(arguments[i], ref defaultArguments, i);
argumentsBuilder.Add(operationFactory.CreateArgumentOperation(argumentKind, parameters[i].GetPublicSymbol(), arguments[i]));
}
// TODO: In case of __arglist, we will have more arguments than parameters,
// set the parameter to null for __arglist argument for now.
// https://github.com/dotnet/roslyn/issues/19673
for (; i < arguments.Length; ++i)
{
var argumentKind = defaultArguments[i] ? ArgumentKind.DefaultValue : ArgumentKind.Explicit;
argumentsBuilder.Add(operationFactory.CreateArgumentOperation(argumentKind, null, arguments[i]));
}
Debug.Assert(methodOrIndexer.GetIsVararg() ^ parameters.Length == arguments.Length);
return argumentsBuilder.ToImmutableAndFree();
}
return BuildArgumentsInEvaluationOrder(
operationFactory,
methodOrIndexer,
argsToParamsOpt,
defaultArguments,
arguments);
}
private static ArgumentKind GetArgumentKind(BoundExpression argument, ref BitVector defaultArguments, int i)
{
ArgumentKind argumentKind;
if (defaultArguments[i])
{
argumentKind = ArgumentKind.DefaultValue;
}
else if (argument.IsParamsArrayOrCollection)
{
argumentKind = argument.Type?.IsSZArray() == true ? ArgumentKind.ParamArray : ArgumentKind.ParamCollection;
}
else
{
argumentKind = ArgumentKind.Explicit;
}
return argumentKind;
}
// This fills in the arguments in evaluation order.
private static ImmutableArray<IArgumentOperation> BuildArgumentsInEvaluationOrder(
CSharpOperationFactory operationFactory,
Symbol methodOrIndexer,
ImmutableArray<int> argsToParamsOpt,
BitVector defaultArguments,
ImmutableArray<BoundExpression> arguments)
{
ImmutableArray<ParameterSymbol> parameters = methodOrIndexer.GetParameters();
ArrayBuilder<IArgumentOperation> argumentsInEvaluationBuilder = ArrayBuilder<IArgumentOperation>.GetInstance(parameters.Length);
// First, fill in all the explicitly provided arguments.
for (int a = 0; a < arguments.Length; ++a)
{
BoundExpression argument = arguments[a];
int p = (!argsToParamsOpt.IsDefault) ? argsToParamsOpt[a] : a;
var parameter = parameters[p];
ArgumentKind kind = GetArgumentKind(argument, ref defaultArguments, a);
argumentsInEvaluationBuilder.Add(operationFactory.CreateArgumentOperation(kind, parameter.GetPublicSymbol(), argument));
}
Debug.Assert(argumentsInEvaluationBuilder.All(static arg => arg is not null));
return argumentsInEvaluationBuilder.ToImmutableAndFree();
}
internal static ImmutableArray<BoundNode> CreateInvalidChildrenFromArgumentsExpression(BoundNode? receiverOpt, ImmutableArray<BoundExpression> arguments, BoundExpression? additionalNodeOpt = null)
{
var builder = ArrayBuilder<BoundNode>.GetInstance();
if (receiverOpt != null
&& (!receiverOpt.WasCompilerGenerated
|| (receiverOpt.Kind != BoundKind.ThisReference
&& receiverOpt.Kind != BoundKind.BaseReference
&& receiverOpt.Kind != BoundKind.ObjectOrCollectionValuePlaceholder)))
{
builder.Add(receiverOpt);
}
builder.AddRange(StaticCast<BoundNode>.From(arguments));
builder.AddIfNotNull(additionalNodeOpt);
return builder.ToImmutableAndFree();
}
internal ImmutableArray<IOperation> GetAnonymousObjectCreationInitializers(
ImmutableArray<BoundExpression> arguments,
ImmutableArray<BoundAnonymousPropertyDeclaration> declarations,
SyntaxNode syntax,
ITypeSymbol type,
bool isImplicit)
{
// For error cases and non-assignment initializers, the binder generates only the argument.
Debug.Assert(arguments.Length >= declarations.Length);
var builder = ArrayBuilder<IOperation>.GetInstance(arguments.Length);
var currentDeclarationIndex = 0;
for (int i = 0; i < arguments.Length; i++)
{
IOperation value = Create(arguments[i]);
IOperation target;
bool isImplicitAssignment;
// Synthesize an implicit receiver for property reference being assigned.
var instance = new InstanceReferenceOperation(
referenceKind: InstanceReferenceKind.ImplicitReceiver,
semanticModel: _semanticModel,
syntax: syntax,
type: type,
isImplicit: true);
// Find matching declaration for the current argument.
PropertySymbol property = AnonymousTypeManager.GetAnonymousTypeProperty(type.GetSymbol<NamedTypeSymbol>(), i);
BoundAnonymousPropertyDeclaration? anonymousProperty = getDeclaration(declarations, property, ref currentDeclarationIndex);
if (anonymousProperty is null)
{
// No matching declaration, synthesize a property reference to be assigned.
target = new PropertyReferenceOperation(
property.GetPublicSymbol(),
constrainedToType: null,
arguments: ImmutableArray<IArgumentOperation>.Empty,
instance,
semanticModel: _semanticModel,
syntax: value.Syntax,
type: property.Type.GetPublicSymbol(),
isImplicit: true);
isImplicitAssignment = true;
}
else
{
target = new PropertyReferenceOperation(anonymousProperty.Property.GetPublicSymbol(),
constrainedToType: null,
ImmutableArray<IArgumentOperation>.Empty,
instance,
_semanticModel,
anonymousProperty.Syntax,
anonymousProperty.GetPublicTypeSymbol(),
anonymousProperty.WasCompilerGenerated);
isImplicitAssignment = isImplicit;
}
var assignmentSyntax = value.Syntax?.Parent ?? syntax;
ITypeSymbol? assignmentType = target.Type;
bool isRef = false;
var assignment = new SimpleAssignmentOperation(isRef, target, value, _semanticModel, assignmentSyntax, assignmentType, value.GetConstantValue(), isImplicitAssignment);
builder.Add(assignment);
}
Debug.Assert(currentDeclarationIndex == declarations.Length);
return builder.ToImmutableAndFree();
static BoundAnonymousPropertyDeclaration? getDeclaration(ImmutableArray<BoundAnonymousPropertyDeclaration> declarations, PropertySymbol currentProperty, ref int currentDeclarationIndex)
{
if (currentDeclarationIndex >= declarations.Length)
{
return null;
}
var currentDeclaration = declarations[currentDeclarationIndex];
if (currentProperty.MemberIndexOpt == currentDeclaration.Property.MemberIndexOpt)
{
currentDeclarationIndex++;
return currentDeclaration;
}
return null;
}
}
internal class Helper
{
internal static bool IsPostfixIncrementOrDecrement(CSharp.UnaryOperatorKind operatorKind)
{
switch (operatorKind.Operator())
{
case CSharp.UnaryOperatorKind.PostfixIncrement:
case CSharp.UnaryOperatorKind.PostfixDecrement:
return true;
default:
return false;
}
}
internal static bool IsDecrement(CSharp.UnaryOperatorKind operatorKind)
{
switch (operatorKind.Operator())
{
case CSharp.UnaryOperatorKind.PrefixDecrement:
case CSharp.UnaryOperatorKind.PostfixDecrement:
return true;
default:
return false;
}
}
internal static UnaryOperatorKind DeriveUnaryOperatorKind(CSharp.UnaryOperatorKind operatorKind)
{
switch (operatorKind.Operator())
{
case CSharp.UnaryOperatorKind.UnaryPlus:
return UnaryOperatorKind.Plus;
case CSharp.UnaryOperatorKind.UnaryMinus:
return UnaryOperatorKind.Minus;
case CSharp.UnaryOperatorKind.LogicalNegation:
return UnaryOperatorKind.Not;
case CSharp.UnaryOperatorKind.BitwiseComplement:
return UnaryOperatorKind.BitwiseNegation;
case CSharp.UnaryOperatorKind.True:
return UnaryOperatorKind.True;
case CSharp.UnaryOperatorKind.False:
return UnaryOperatorKind.False;
}
return UnaryOperatorKind.None;
}
internal static BinaryOperatorKind DeriveBinaryOperatorKind(CSharp.BinaryOperatorKind operatorKind)
{
switch (operatorKind.OperatorWithLogical())
{
case CSharp.BinaryOperatorKind.Addition:
return BinaryOperatorKind.Add;
case CSharp.BinaryOperatorKind.Subtraction:
return BinaryOperatorKind.Subtract;
case CSharp.BinaryOperatorKind.Multiplication:
return BinaryOperatorKind.Multiply;
case CSharp.BinaryOperatorKind.Division:
return BinaryOperatorKind.Divide;
case CSharp.BinaryOperatorKind.Remainder:
return BinaryOperatorKind.Remainder;
case CSharp.BinaryOperatorKind.LeftShift:
return BinaryOperatorKind.LeftShift;
case CSharp.BinaryOperatorKind.RightShift:
return BinaryOperatorKind.RightShift;
case CSharp.BinaryOperatorKind.UnsignedRightShift:
return BinaryOperatorKind.UnsignedRightShift;
case CSharp.BinaryOperatorKind.And:
return BinaryOperatorKind.And;
case CSharp.BinaryOperatorKind.Or:
return BinaryOperatorKind.Or;
case CSharp.BinaryOperatorKind.Xor:
return BinaryOperatorKind.ExclusiveOr;
case CSharp.BinaryOperatorKind.LessThan:
return BinaryOperatorKind.LessThan;
case CSharp.BinaryOperatorKind.LessThanOrEqual:
return BinaryOperatorKind.LessThanOrEqual;
case CSharp.BinaryOperatorKind.Equal:
return BinaryOperatorKind.Equals;
case CSharp.BinaryOperatorKind.NotEqual:
return BinaryOperatorKind.NotEquals;
case CSharp.BinaryOperatorKind.GreaterThanOrEqual:
return BinaryOperatorKind.GreaterThanOrEqual;
case CSharp.BinaryOperatorKind.GreaterThan:
return BinaryOperatorKind.GreaterThan;
case CSharp.BinaryOperatorKind.LogicalAnd:
return BinaryOperatorKind.ConditionalAnd;
case CSharp.BinaryOperatorKind.LogicalOr:
return BinaryOperatorKind.ConditionalOr;
}
return BinaryOperatorKind.None;
}
}
}
}
|