File: Syntax\GreenNode.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Syntax.InternalSyntax;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis
{
    [DebuggerDisplay("{GetDebuggerDisplay(), nq}")]
    internal abstract partial class GreenNode
    {
        private string GetDebuggerDisplay()
        {
            return this.GetType().Name + " " + this.KindText + " " + this.ToString();
        }
 
        internal const int ListKind = 1;
 
        // Pack the kind, node-flags, slot-count, and full-width into 64bits. Note: if we need more bits in the future
        // (say for additional node-flags), we can always directly use a packed int64 here, and manage where all these
        // bits go manually.
 
        /// <summary>
        /// Value used to indicate the slot count was too large to be encoded directly in our <see cref="_nodeFlagsAndSlotCount"/>
        /// value.  Callers will have to store the value elsewhere and retrieve the full value themselves.
        /// </summary>
        protected const int SlotCountTooLarge = 0b0000000000001111;
 
        private readonly ushort _kind;
        private NodeFlagsAndSlotCount _nodeFlagsAndSlotCount;
        private int _fullWidth;
 
        private static readonly ConditionalWeakTable<GreenNode, DiagnosticInfo[]> s_diagnosticsTable =
            new ConditionalWeakTable<GreenNode, DiagnosticInfo[]>();
 
        private static readonly ConditionalWeakTable<GreenNode, SyntaxAnnotation[]> s_annotationsTable =
            new ConditionalWeakTable<GreenNode, SyntaxAnnotation[]>();
 
        private static readonly DiagnosticInfo[] s_noDiagnostics = Array.Empty<DiagnosticInfo>();
        private static readonly SyntaxAnnotation[] s_noAnnotations = Array.Empty<SyntaxAnnotation>();
        private static readonly IEnumerable<SyntaxAnnotation> s_noAnnotationsEnumerable = SpecializedCollections.EmptyEnumerable<SyntaxAnnotation>();
 
        protected GreenNode(ushort kind)
        {
            _kind = kind;
        }
 
        protected GreenNode(ushort kind, int fullWidth)
        {
            _kind = kind;
            _fullWidth = fullWidth;
        }
 
        protected GreenNode(ushort kind, DiagnosticInfo[]? diagnostics, int fullWidth)
        {
            _kind = kind;
            _fullWidth = fullWidth;
            if (diagnostics?.Length > 0)
            {
                SetFlags(NodeFlags.ContainsDiagnostics);
                s_diagnosticsTable.Add(this, diagnostics);
            }
        }
 
        protected GreenNode(ushort kind, DiagnosticInfo[]? diagnostics)
        {
            _kind = kind;
            if (diagnostics?.Length > 0)
            {
                SetFlags(NodeFlags.ContainsDiagnostics);
                s_diagnosticsTable.Add(this, diagnostics);
            }
        }
 
        protected GreenNode(ushort kind, DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations) :
            this(kind, diagnostics)
        {
            if (annotations?.Length > 0)
            {
                foreach (var annotation in annotations)
                {
                    if (annotation == null) throw new ArgumentException(paramName: nameof(annotations), message: "" /*CSharpResources.ElementsCannotBeNull*/);
                }
 
                SetFlags(NodeFlags.HasAnnotationsDirectly | NodeFlags.ContainsAnnotations);
                s_annotationsTable.Add(this, annotations);
            }
        }
 
        protected GreenNode(ushort kind, DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations, int fullWidth) :
            this(kind, diagnostics, fullWidth)
        {
            if (annotations?.Length > 0)
            {
                foreach (var annotation in annotations)
                {
                    if (annotation == null) throw new ArgumentException(paramName: nameof(annotations), message: "" /*CSharpResources.ElementsCannotBeNull*/);
                }
 
                SetFlags(NodeFlags.HasAnnotationsDirectly | NodeFlags.ContainsAnnotations);
                s_annotationsTable.Add(this, annotations);
            }
        }
 
        protected void AdjustFlagsAndWidth(GreenNode node)
        {
            RoslynDebug.Assert(node != null, "PERF: caller must ensure that node!=null, we do not want to re-check that here.");
            SetFlags(node.Flags & NodeFlags.InheritMask);
            _fullWidth += node._fullWidth;
        }
 
        public abstract string Language { get; }
 
        #region Kind 
        public int RawKind
        {
            get { return _kind; }
        }
 
        public bool IsList
        {
            get
            {
                return RawKind == ListKind;
            }
        }
 
        public abstract string KindText { get; }
 
        public virtual bool IsStructuredTrivia => false;
        public virtual bool IsDirective => false;
        public virtual bool IsToken => false;
        public virtual bool IsTrivia => false;
        public virtual bool IsSkippedTokensTrivia => false;
        public virtual bool IsDocumentationCommentTrivia => false;
 
        #endregion
 
        #region Slots 
        public int SlotCount
        {
            get
            {
                var count = _nodeFlagsAndSlotCount.SmallSlotCount;
                return count == SlotCountTooLarge ? GetSlotCount() : count;
            }
 
            protected set
            {
                Debug.Assert(value <= byte.MaxValue);
                _nodeFlagsAndSlotCount.SmallSlotCount = (byte)value;
            }
        }
 
        internal abstract GreenNode? GetSlot(int index);
 
        internal GreenNode GetRequiredSlot(int index)
        {
            var node = GetSlot(index);
            RoslynDebug.Assert(node is object);
            return node;
        }
 
        /// <summary>
        /// Called when <see cref="NodeFlagsAndSlotCount.SmallSlotCount"/> returns a value of <see cref="SlotCountTooLarge"/>.
        /// </summary>
        protected virtual int GetSlotCount()
        {
            // This should only be called for nodes that couldn't store their slot count effectively in our
            // _nodeFlagsAndSlotCount field.  The only nodes that cannot do that are the `WithManyChildren` list types.
            // All of which should be subclassing this method.
            throw ExceptionUtilities.Unreachable();
        }
 
        public virtual int GetSlotOffset(int index)
        {
            int offset = 0;
            for (int i = 0; i < index; i++)
            {
                var child = this.GetSlot(i);
                if (child != null)
                {
                    offset += child.FullWidth;
                }
            }
 
            return offset;
        }
 
        internal Syntax.InternalSyntax.ChildSyntaxList ChildNodesAndTokens()
        {
            return new Syntax.InternalSyntax.ChildSyntaxList(this);
        }
 
        /// <summary>
        /// Enumerates all green nodes of the tree rooted by this node (including this node).  This includes normal
        /// nodes, list nodes, and tokens.  The nodes will be returned in depth-first order.  This will not descend 
        /// into trivia or structured trivia.
        /// </summary>
        public NodeEnumerable EnumerateNodes()
            => new NodeEnumerable(this);
 
        /// <summary>
        /// Find the slot that contains the given offset.
        /// </summary>
        /// <param name="offset">The target offset. Must be between 0 and <see cref="FullWidth"/>.</param>
        /// <returns>The slot index of the slot containing the given offset.</returns>
        /// <remarks>
        /// The base implementation is a linear search. This should be overridden
        /// if a derived class can implement it more efficiently.
        /// </remarks>
        public virtual int FindSlotIndexContainingOffset(int offset)
        {
            Debug.Assert(0 <= offset && offset < FullWidth);
 
            int i;
            int accumulatedWidth = 0;
            for (i = 0; ; i++)
            {
                Debug.Assert(i < SlotCount);
                var child = GetSlot(i);
                if (child != null)
                {
                    accumulatedWidth += child.FullWidth;
                    if (offset < accumulatedWidth)
                    {
                        break;
                    }
                }
            }
 
            return i;
        }
 
        #endregion
 
        #region Flags 
 
        /// <summary>
        /// Special flags a node can have.  Note: while this is typed as being `ushort`, we can only practically use 12
        /// of those 16 bits as we use the remaining 4 bits to store the slot count of a node.
        /// </summary>
        [Flags]
        internal enum NodeFlags : ushort
        {
            None = 0,
            /// <summary>
            /// If this node is missing or not.  We use a non-zero value for the not-missing case so that this value
            /// automatically merges upwards when building parent nodes.  In other words, once we have one node that is
            /// not-missing, all nodes above it are definitely not-missing as well.
            /// </summary>
            IsNotMissing = 1 << 0,
            /// <summary>
            /// If this node directly has annotations (not its descendants).  <see cref="ContainsAnnotations"/> can be
            /// used to determine if a node or any of its descendants has annotations.
            /// </summary>
            HasAnnotationsDirectly = 1 << 1,
 
            FactoryContextIsInAsync = 1 << 2,
            FactoryContextIsInQuery = 1 << 3,
            FactoryContextIsInIterator = FactoryContextIsInQuery,  // VB does not use "InQuery", but uses "InIterator" instead
 
            // Flags that are inherited upwards when building parent nodes.  They should all start with "Contains" to
            // indicate that the information could be found on it or anywhere in its children.
 
            /// <summary>
            /// If this node, or any of its descendants has annotations attached to them.
            /// </summary>
            ContainsAnnotations = 1 << 4,
            /// <summary>
            /// If this node, or any of its descendants has attributes attached to it.
            /// </summary>
            ContainsAttributes = 1 << 5,
            ContainsDiagnostics = 1 << 6,
            ContainsDirectives = 1 << 7,
            ContainsSkippedText = 1 << 8,
            ContainsStructuredTrivia = 1 << 9,
 
            InheritMask = IsNotMissing | ContainsAnnotations | ContainsAttributes | ContainsDiagnostics | ContainsDirectives | ContainsSkippedText | ContainsStructuredTrivia,
        }
 
        internal NodeFlags Flags
        {
            get { return this._nodeFlagsAndSlotCount.NodeFlags; }
        }
 
        internal void SetFlags(NodeFlags flags)
        {
            _nodeFlagsAndSlotCount.NodeFlags |= flags;
        }
 
        internal void ClearFlags(NodeFlags flags)
        {
            _nodeFlagsAndSlotCount.NodeFlags &= ~flags;
        }
 
        internal bool IsMissing
        {
            get
            {
                // flag has reversed meaning hence "=="
                return (this.Flags & NodeFlags.IsNotMissing) == 0;
            }
        }
 
        internal bool ParsedInAsync
        {
            get
            {
                return (this.Flags & NodeFlags.FactoryContextIsInAsync) != 0;
            }
        }
 
        internal bool ParsedInQuery
        {
            get
            {
                return (this.Flags & NodeFlags.FactoryContextIsInQuery) != 0;
            }
        }
 
        internal bool ParsedInIterator
        {
            get
            {
                return (this.Flags & NodeFlags.FactoryContextIsInIterator) != 0;
            }
        }
 
        public bool ContainsSkippedText
        {
            get
            {
                return (this.Flags & NodeFlags.ContainsSkippedText) != 0;
            }
        }
 
        public bool ContainsStructuredTrivia
        {
            get
            {
                return (this.Flags & NodeFlags.ContainsStructuredTrivia) != 0;
            }
        }
 
        public bool ContainsDirectives
        {
            get
            {
                return (this.Flags & NodeFlags.ContainsDirectives) != 0;
            }
        }
 
        public bool ContainsAttributes
        {
            get
            {
                return (this.Flags & NodeFlags.ContainsAttributes) != 0;
            }
        }
 
        public bool ContainsDiagnostics
        {
            get
            {
                return (this.Flags & NodeFlags.ContainsDiagnostics) != 0;
            }
        }
 
        public bool ContainsAnnotations
        {
            get
            {
                return (this.Flags & NodeFlags.ContainsAnnotations) != 0;
            }
        }
 
        public bool HasAnnotationsDirectly
        {
            get
            {
                return (this.Flags & NodeFlags.HasAnnotationsDirectly) != 0;
            }
        }
 
        #endregion
 
        #region Spans
        public int FullWidth
        {
            get
            {
                return _fullWidth;
            }
 
            protected set
            {
                _fullWidth = value;
            }
        }
 
        public virtual int Width
        {
            get
            {
                return _fullWidth - this.GetLeadingTriviaWidth() - this.GetTrailingTriviaWidth();
            }
        }
 
        public virtual int GetLeadingTriviaWidth()
        {
            return this.FullWidth != 0 ?
                this.GetFirstTerminal()!.GetLeadingTriviaWidth() :
                0;
        }
 
        public virtual int GetTrailingTriviaWidth()
        {
            return this.FullWidth != 0 ?
                this.GetLastTerminal()!.GetTrailingTriviaWidth() :
                0;
        }
 
        public bool HasLeadingTrivia
        {
            get
            {
                return this.GetLeadingTriviaWidth() != 0;
            }
        }
 
        public bool HasTrailingTrivia
        {
            get
            {
                return this.GetTrailingTriviaWidth() != 0;
            }
        }
        #endregion
 
        #region Annotations 
        public bool HasAnnotations(string annotationKind)
        {
            var annotations = this.GetAnnotations();
            if (annotations == s_noAnnotations)
            {
                return false;
            }
 
            foreach (var a in annotations)
            {
                if (a.Kind == annotationKind)
                {
                    return true;
                }
            }
 
            return false;
        }
 
        public bool HasAnnotations(IEnumerable<string> annotationKinds)
        {
            var annotations = this.GetAnnotations();
            if (annotations == s_noAnnotations)
            {
                return false;
            }
 
            foreach (var a in annotations)
            {
                if (annotationKinds.Contains(a.Kind))
                {
                    return true;
                }
            }
 
            return false;
        }
 
        public bool HasAnnotation([NotNullWhen(true)] SyntaxAnnotation? annotation)
        {
            var annotations = this.GetAnnotations();
            if (annotations == s_noAnnotations)
            {
                return false;
            }
 
            foreach (var a in annotations)
            {
                if (a == annotation)
                {
                    return true;
                }
            }
 
            return false;
        }
 
        public IEnumerable<SyntaxAnnotation> GetAnnotations(string annotationKind)
        {
            if (string.IsNullOrWhiteSpace(annotationKind))
            {
                throw new ArgumentNullException(nameof(annotationKind));
            }
 
            var annotations = this.GetAnnotations();
 
            if (annotations == s_noAnnotations)
            {
                return s_noAnnotationsEnumerable;
            }
 
            return GetAnnotationsSlow(annotations, annotationKind);
        }
 
        private static IEnumerable<SyntaxAnnotation> GetAnnotationsSlow(SyntaxAnnotation[] annotations, string annotationKind)
        {
            foreach (var annotation in annotations)
            {
                if (annotation.Kind == annotationKind)
                {
                    yield return annotation;
                }
            }
        }
 
        public IEnumerable<SyntaxAnnotation> GetAnnotations(IEnumerable<string> annotationKinds)
        {
            if (annotationKinds == null)
            {
                throw new ArgumentNullException(nameof(annotationKinds));
            }
 
            var annotations = this.GetAnnotations();
 
            if (annotations == s_noAnnotations)
            {
                return s_noAnnotationsEnumerable;
            }
 
            return GetAnnotationsSlow(annotations, annotationKinds);
        }
 
        private static IEnumerable<SyntaxAnnotation> GetAnnotationsSlow(SyntaxAnnotation[] annotations, IEnumerable<string> annotationKinds)
        {
            foreach (var annotation in annotations)
            {
                if (annotationKinds.Contains(annotation.Kind))
                {
                    yield return annotation;
                }
            }
        }
 
        public SyntaxAnnotation[] GetAnnotations()
        {
            if (!this.HasAnnotationsDirectly)
                return s_noAnnotations;
 
            var found = s_annotationsTable.TryGetValue(this, out var annotations);
            Debug.Assert(found, "We must be able to find annotations since we had the bit set on ourselves");
            Debug.Assert(annotations != null, "annotations should not be null");
            Debug.Assert(annotations != s_noAnnotations, "annotations should not be s_noAnnotations");
            Debug.Assert(annotations.Length != 0, "annotations should be non-empty");
            return annotations;
        }
 
        internal abstract GreenNode SetAnnotations(SyntaxAnnotation[]? annotations);
 
        #endregion
 
        #region Diagnostics
        internal DiagnosticInfo[] GetDiagnostics()
        {
            if (this.ContainsDiagnostics)
            {
                DiagnosticInfo[]? diags;
                if (s_diagnosticsTable.TryGetValue(this, out diags))
                {
                    return diags;
                }
            }
 
            return s_noDiagnostics;
        }
 
        internal abstract GreenNode SetDiagnostics(DiagnosticInfo[]? diagnostics);
        #endregion
 
        #region Text
 
        public virtual string ToFullString()
        {
            var sb = PooledStringBuilder.GetInstance();
            var writer = new System.IO.StringWriter(sb.Builder, System.Globalization.CultureInfo.InvariantCulture);
            this.WriteTo(writer, leading: true, trailing: true);
            return sb.ToStringAndFree();
        }
 
        public override string ToString()
        {
            var sb = PooledStringBuilder.GetInstance();
            var writer = new System.IO.StringWriter(sb.Builder, System.Globalization.CultureInfo.InvariantCulture);
            this.WriteTo(writer, leading: false, trailing: false);
            return sb.ToStringAndFree();
        }
 
        public void WriteTo(System.IO.TextWriter writer)
        {
            this.WriteTo(writer, leading: true, trailing: true);
        }
 
        protected internal void WriteTo(TextWriter writer, bool leading, bool trailing)
        {
            // Use an actual stack so we can write out deeply recursive structures without overflowing.
            var stack = ArrayBuilder<(GreenNode node, bool leading, bool trailing)>.GetInstance();
            stack.Push((this, leading, trailing));
 
            // Separated out stack processing logic so that it does not unintentionally refer to 
            // "this", "leading" or "trailing".
            processStack(writer, stack);
            stack.Free();
            return;
 
            static void processStack(
                TextWriter writer,
                ArrayBuilder<(GreenNode node, bool leading, bool trailing)> stack)
            {
                while (stack.Count > 0)
                {
                    var current = stack.Pop();
                    var currentNode = current.node;
                    var currentLeading = current.leading;
                    var currentTrailing = current.trailing;
 
                    if (currentNode.IsToken)
                    {
                        currentNode.WriteTokenTo(writer, currentLeading, currentTrailing);
                        continue;
                    }
 
                    if (currentNode.IsTrivia)
                    {
                        currentNode.WriteTriviaTo(writer);
                        continue;
                    }
 
                    var firstIndex = GetFirstNonNullChildIndex(currentNode);
                    var lastIndex = GetLastNonNullChildIndex(currentNode);
 
                    for (var i = lastIndex; i >= firstIndex; i--)
                    {
                        var child = currentNode.GetSlot(i);
                        if (child != null)
                        {
                            var first = i == firstIndex;
                            var last = i == lastIndex;
                            stack.Push((child, currentLeading | !first, currentTrailing | !last));
                        }
                    }
                }
            }
        }
 
        private static int GetFirstNonNullChildIndex(GreenNode node)
        {
            int n = node.SlotCount;
            int firstIndex = 0;
            for (; firstIndex < n; firstIndex++)
            {
                var child = node.GetSlot(firstIndex);
                if (child != null)
                {
                    break;
                }
            }
 
            return firstIndex;
        }
 
        private static int GetLastNonNullChildIndex(GreenNode node)
        {
            int n = node.SlotCount;
            int lastIndex = n - 1;
            for (; lastIndex >= 0; lastIndex--)
            {
                var child = node.GetSlot(lastIndex);
                if (child != null)
                {
                    break;
                }
            }
 
            return lastIndex;
        }
 
        protected virtual void WriteTriviaTo(TextWriter writer)
        {
            throw new NotImplementedException();
        }
 
        protected virtual void WriteTokenTo(TextWriter writer, bool leading, bool trailing)
        {
            throw new NotImplementedException();
        }
 
        #endregion
 
        #region Tokens 
 
        public virtual int RawContextualKind { get { return this.RawKind; } }
        public virtual object? GetValue() { return null; }
        public virtual string GetValueText() { return string.Empty; }
        public virtual GreenNode? GetLeadingTriviaCore() { return null; }
        public virtual GreenNode? GetTrailingTriviaCore() { return null; }
 
        public virtual GreenNode WithLeadingTrivia(GreenNode? trivia)
        {
            return this;
        }
 
        public virtual GreenNode WithTrailingTrivia(GreenNode? trivia)
        {
            return this;
        }
 
        internal GreenNode? GetFirstTerminal()
        {
            GreenNode? node = this;
 
            do
            {
                GreenNode? firstChild = null;
                for (int i = 0, n = node.SlotCount; i < n; i++)
                {
                    var child = node.GetSlot(i);
                    if (child != null)
                    {
                        firstChild = child;
                        break;
                    }
                }
                node = firstChild;
            }
            // Note: it's ok to examine SmallSlotCount here.  All we're trying to do is make sure we have at least one
            // child.  And SmallSlotCount works both for small counts and large counts.  This avoids an unnecessary
            // virtual call for large list nodes.
            while (node?._nodeFlagsAndSlotCount.SmallSlotCount > 0);
 
            return node;
        }
 
        internal GreenNode? GetLastTerminal()
        {
            GreenNode? node = this;
 
            do
            {
                GreenNode? lastChild = null;
                for (int i = node.SlotCount - 1; i >= 0; i--)
                {
                    var child = node.GetSlot(i);
                    if (child != null)
                    {
                        lastChild = child;
                        break;
                    }
                }
                node = lastChild;
            }
            // Note: it's ok to examine SmallSlotCount here.  All we're trying to do is make sure we have at least one
            // child.  And SmallSlotCount works both for small counts and large counts.  This avoids an unnecessary
            // virtual call for large list nodes.
            while (node?._nodeFlagsAndSlotCount.SmallSlotCount > 0);
 
            return node;
        }
 
        internal GreenNode? GetLastNonmissingTerminal()
        {
            GreenNode? node = this;
 
            do
            {
                GreenNode? nonmissingChild = null;
                for (int i = node.SlotCount - 1; i >= 0; i--)
                {
                    var child = node.GetSlot(i);
                    if (child != null && !child.IsMissing)
                    {
                        nonmissingChild = child;
                        break;
                    }
                }
                node = nonmissingChild;
            }
            // Note: it's ok to examine SmallSlotCount here.  All we're trying to do is make sure we have at least one
            // child.  And SmallSlotCount works both for small counts and large counts.  This avoids an unnecessary
            // virtual call for large list nodes.
            while (node?._nodeFlagsAndSlotCount.SmallSlotCount > 0);
 
            return node;
        }
        #endregion
 
        #region Equivalence 
        public virtual bool IsEquivalentTo([NotNullWhen(true)] GreenNode? other)
        {
            if (this == other)
            {
                return true;
            }
 
            if (other == null)
            {
                return false;
            }
 
            return EquivalentToInternal(this, other);
        }
 
        private static bool EquivalentToInternal(GreenNode node1, GreenNode node2)
        {
            if (node1.RawKind != node2.RawKind)
            {
                // A single-element list is usually represented as just a single node,
                // but can be represented as a List node with one child. Move to that
                // child if necessary.
                if (node1.IsList && node1.SlotCount == 1)
                {
                    node1 = node1.GetRequiredSlot(0);
                }
 
                if (node2.IsList && node2.SlotCount == 1)
                {
                    node2 = node2.GetRequiredSlot(0);
                }
 
                if (node1.RawKind != node2.RawKind)
                {
                    return false;
                }
            }
 
            if (node1._fullWidth != node2._fullWidth)
            {
                return false;
            }
 
            var n = node1.SlotCount;
            if (n != node2.SlotCount)
            {
                return false;
            }
 
            for (int i = 0; i < n; i++)
            {
                var node1Child = node1.GetSlot(i);
                var node2Child = node2.GetSlot(i);
                if (node1Child != null && node2Child != null && !node1Child.IsEquivalentTo(node2Child))
                {
                    return false;
                }
            }
 
            return true;
        }
        #endregion
 
        public abstract SyntaxNode GetStructure(SyntaxTrivia parentTrivia);
 
        #region Factories 
 
        public abstract SyntaxToken CreateSeparator(SyntaxNode element);
        public abstract bool IsTriviaWithEndOfLine(); // trivia node has end of line
 
        /*
         * There are 3 overloads of this, because most callers already know what they have is a List<T> and only transform it.
         * In those cases List<TFrom> performs much better.
         * In other cases, the type is unknown / is IEnumerable<T>, where we try to find the best match.
         * There is another overload for IReadOnlyList, since most collections already implement this, so checking for it will
         * perform better then copying to a List<T>, though not as good as List<T> directly.
         */
        public static GreenNode? CreateList<TFrom>(IEnumerable<TFrom>? enumerable, Func<TFrom, GreenNode> select)
            => enumerable switch
            {
                null => null,
                List<TFrom> l => CreateList(l, select),
                IReadOnlyList<TFrom> l => CreateList(l, select),
                _ => CreateList(enumerable.ToList(), select)
            };
 
        public static GreenNode? CreateList<TFrom>(List<TFrom> list, Func<TFrom, GreenNode> select)
        {
            switch (list.Count)
            {
                case 0:
                    return null;
                case 1:
                    return select(list[0]);
                case 2:
                    return Syntax.InternalSyntax.SyntaxList.List(select(list[0]), select(list[1]));
                case 3:
                    return Syntax.InternalSyntax.SyntaxList.List(select(list[0]), select(list[1]), select(list[2]));
                default:
                    {
                        var array = new ArrayElement<GreenNode>[list.Count];
                        for (int i = 0; i < array.Length; i++)
                            array[i].Value = select(list[i]);
                        return Syntax.InternalSyntax.SyntaxList.List(array);
                    }
            }
        }
 
        public static GreenNode? CreateList<TFrom>(IReadOnlyList<TFrom> list, Func<TFrom, GreenNode> select)
        {
            switch (list.Count)
            {
                case 0:
                    return null;
                case 1:
                    return select(list[0]);
                case 2:
                    return Syntax.InternalSyntax.SyntaxList.List(select(list[0]), select(list[1]));
                case 3:
                    return Syntax.InternalSyntax.SyntaxList.List(select(list[0]), select(list[1]), select(list[2]));
                default:
                    {
                        var array = new ArrayElement<GreenNode>[list.Count];
                        for (int i = 0; i < array.Length; i++)
                            array[i].Value = select(list[i]);
                        return Syntax.InternalSyntax.SyntaxList.List(array);
                    }
            }
        }
 
        public SyntaxNode CreateRed()
        {
            return CreateRed(null, 0);
        }
 
        internal abstract SyntaxNode CreateRed(SyntaxNode? parent, int position);
 
        #endregion
 
        #region Caching
 
        internal const int MaxCachedChildNum = 3;
 
        internal bool IsCacheable
        {
            get
            {
                return ((this.Flags & NodeFlags.InheritMask) == NodeFlags.IsNotMissing) &&
                    this.SlotCount <= GreenNode.MaxCachedChildNum;
            }
        }
 
        internal int GetCacheHash()
        {
            Debug.Assert(this.IsCacheable);
 
            int code = (int)(this.Flags) ^ this.RawKind;
            int cnt = this.SlotCount;
            for (int i = 0; i < cnt; i++)
            {
                var child = GetSlot(i);
                if (child != null)
                {
                    code = Hash.Combine(RuntimeHelpers.GetHashCode(child), code);
                }
            }
 
            return code & Int32.MaxValue;
        }
 
        internal bool IsCacheEquivalent(int kind, NodeFlags flags, GreenNode? child1)
        {
            Debug.Assert(this.IsCacheable);
 
            return this.RawKind == kind &&
                this.Flags == flags &&
                this.SlotCount == 1 &&
                this.GetSlot(0) == child1;
        }
 
        internal bool IsCacheEquivalent(int kind, NodeFlags flags, GreenNode? child1, GreenNode? child2)
        {
            Debug.Assert(this.IsCacheable);
 
            return this.RawKind == kind &&
                this.Flags == flags &&
                this.SlotCount == 2 &&
                this.GetSlot(0) == child1 &&
                this.GetSlot(1) == child2;
        }
 
        internal bool IsCacheEquivalent(int kind, NodeFlags flags, GreenNode? child1, GreenNode? child2, GreenNode? child3)
        {
            Debug.Assert(this.IsCacheable);
 
            return this.RawKind == kind &&
                this.Flags == flags &&
                this.SlotCount == 3 &&
                this.GetSlot(0) == child1 &&
                this.GetSlot(1) == child2 &&
                this.GetSlot(2) == child3;
        }
        #endregion //Caching
 
        /// <summary>
        /// Add an error to the given node, creating a new node that is the same except it has no parent,
        /// and has the given error attached to it. The error span is the entire span of this node.
        /// </summary>
        /// <param name="err">The error to attach to this node</param>
        /// <returns>A new node, with no parent, that has this error added to it.</returns>
        /// <remarks>Since nodes are immutable, the only way to create nodes with errors attached is to create a node without an error,
        /// then add an error with this method to create another node.</remarks>
        internal GreenNode AddError(DiagnosticInfo err)
        {
            DiagnosticInfo[] errorInfos;
 
            // If the green node already has errors, add those on.
            if (GetDiagnostics() == null)
            {
                errorInfos = new[] { err };
            }
            else
            {
                // Add the error to the error list.
                errorInfos = GetDiagnostics();
                var length = errorInfos.Length;
                Array.Resize(ref errorInfos, length + 1);
                errorInfos[length] = err;
            }
 
            // Get a new green node with the errors added on.
            return SetDiagnostics(errorInfos);
        }
    }
}