Skip to content

[API proposal] Collection expressions: IOperation support #70631

@cston

Description

@cston

Background and Motivation

Provide IOperation support for collection expressions.

Proposed API

namespace Microsoft.CodeAnalysis.Operations
{
    /// <summary>
    /// Represents a collection expression.
    /// <para>
    ///   Current usage:
    ///   (1) C# collection expression.
    /// </para>
    /// </summary>
    public interface ICollectionExpressionOperation : IOperation
    {
        /// <summary>
        /// Method used to construct the collection.
        /// <para>
        ///   If the collection type is an array, span, array interface, or type parameter, the method is null;
        ///   if the collection type has a [CollectionBuilder] attribute, the method is the builder method;
        ///   otherwise, the method is the collection type constructor.
        /// </para>
        /// </summary>
        IMethodSymbol? ConstructMethod { get; }

        /// <summary>
        /// Collection expression elements.
        /// <para>
        ///   If the element is an expression, the entry is converted to the target element type;
        ///   otherwise, the entry is an ISpreadOperation.
        /// </para>
        /// </summary>
        ImmutableArray<IOperation> Elements { get; }
    }

    /// <summary>
    /// Represents a collection expression spread element.
    /// <para>
    ///   Current usage:
    ///   (1) C# spread element.
    /// </para>
    /// </summary>
    public interface ISpreadOperation : IOperation
    {
        /// <summary>
        /// Collection being spread.
        /// </summary>
        IOperation Operand { get; }

        /// <summary>
        /// Type of the collection iterator item.
        /// </summary>
        ITypeSymbol ItemType { get; }

        /// <summary>
        /// Conversion from the type of the iterator item to the target element type
        /// of the containing collection expression.
        /// </summary>
        CommonConversion ItemConversion { get; }
    }
}

namespace Microsoft.CodeAnalysis.CSharp
{
    public readonly struct Conversion
    {
        // ...

        public bool IsCollectionExpression { get; }
    }

    public static class CSharpExtensions
    {
        // ...

        /// <summary>
        /// Gets the underlying item <see cref="Conversion"/> information from this <see cref="ISpreadOperation"/>.
        /// </summary>
        public static Conversion GetSpreadItemConversion(this ISpreadOperation spread);
    }
}

Usage Examples

Span:

// Span<object> x = [1];

ICollectionExpressionOperation (1 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: System.Span<System.Object>) (Syntax: '[1]')
    Elements(1):
        IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: '1')
        Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null)
        Operand:
            ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1')

Collection initializer type:

// List<object> x = [1];

ICollectionExpressionOperation (1 elements, ConstructMethod: System.Collections.Generic.List<System.Object>..ctor()) (OperationKind.CollectionExpression, Type: System.Collections.Generic.List<System.Object>) (Syntax: '[1]')
  Elements(1):
      IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: '1')
        Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null)
        Operand:
          ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1')

Builder method:

// MyCollection<object> x = [1];

// [CollectionBuilder(typeof(MyCollectionBuilder), "Create")]
// struct MyCollection<T> : IEnumerable<T>  { ... }

// class MyCollectionBuilder
// {
//     public static MyCollection<T> Create<T>(ReadOnlySpan<T> items) { ... }
// }

ICollectionExpressionOperation (1 elements, ConstructMethod: MyCollection<System.Object> MyCollectionBuilder.Create<System.Object>(System.ReadOnlySpan<System.Object> items)) (OperationKind.CollectionExpression, Type: MyCollection<System.Object>) (Syntax: '[1]')
  Elements(1):
      IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: '1')
        Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null)
        Operand:
          ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1')

Spread:

// int[] x = ...;
// object[] y = [..x];

ICollectionExpressionOperation (1 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: System.Object[]) (Syntax: '[..x]')
  Elements(1):
      ISpreadOperation (ItemType: System.Int32) (OperationKind.Spread, Type: null, IsImplicit) (Syntax: '..x')
        Operand:
          ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Int32[]) (Syntax: 'x')
        ItemConversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null)
          (Boxing)

Alternative Designs

Risks

Relates to test plan: #66418

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions