< Summary

Line coverage
9%
Covered lines: 18
Uncovered lines: 178
Coverable lines: 196
Total lines: 589
Line coverage: 9.1%
Branch coverage
0%
Covered branches: 1
Total branches: 106
Branch coverage: 0.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/Common/src/System/Text/ValueStringBuilder.AppendSpanFormattable.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4namespace System.Text
 5{
 6    internal ref partial struct ValueStringBuilder
 7    {
 8        internal void AppendSpanFormattable<T>(T value, string? format = null, IFormatProvider? provider = null) where T
 9        {
 010            if (value.TryFormat(_chars.Slice(_pos), out int charsWritten, format, provider))
 11            {
 012                _pos += charsWritten;
 13            }
 14            else
 15            {
 016                Append(value.ToString(format, provider));
 17            }
 018        }
 19    }
 20}
 21

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/Common/src/System/Text/ValueStringBuilder.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Diagnostics;
 6using System.Runtime.CompilerServices;
 7using System.Runtime.InteropServices;
 8
 9#nullable enable
 10
 11namespace System.Text
 12{
 13    [DebuggerDisplay("{DebuggerDisplay,nq}")]
 14    internal ref partial struct ValueStringBuilder
 15    {
 16        private char[]? _arrayToReturnToPool;
 17        private Span<char> _chars;
 18        private int _pos;
 19
 20        public ValueStringBuilder(Span<char> initialBuffer)
 21        {
 505922            _arrayToReturnToPool = null;
 505923            _chars = initialBuffer;
 505924            _pos = 0;
 505925        }
 26
 27        public ValueStringBuilder(int initialCapacity)
 28        {
 029            _arrayToReturnToPool = ArrayPool<char>.Shared.Rent(initialCapacity);
 030            _chars = _arrayToReturnToPool;
 031            _pos = 0;
 032        }
 33
 34        public int Length
 35        {
 036            get => _pos;
 37            set
 38            {
 505439                Debug.Assert(value >= 0);
 505440                Debug.Assert(value <= _chars.Length);
 505441                _pos = value;
 505442            }
 43        }
 44
 1011845        public int Capacity => _chars.Length;
 46
 47        public void EnsureCapacity(int capacity)
 48        {
 49            // This is not expected to be called this with negative capacity
 050            Debug.Assert(capacity >= 0);
 51
 52            // If the caller has a bug and calls this with negative capacity, make sure to call Grow to throw an excepti
 053            if ((uint)capacity > (uint)_chars.Length)
 054                Grow(capacity - _pos);
 055        }
 56
 57        /// <summary>
 58        /// Ensures that the builder is terminated with a NUL character.
 59        /// </summary>
 60        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 61        public void NullTerminate()
 62        {
 063            EnsureCapacity(_pos + 1);
 064            _chars[_pos] = '\0';
 065        }
 66
 67        /// <summary>
 68        /// Get a pinnable reference to the builder.
 69        /// Does not ensure there is a null char after <see cref="Length"/>
 70        /// This overload is pattern matched in the C# 7.3+ compiler so you can omit
 71        /// the explicit method call, and write eg "fixed (char* c = builder)"
 72        /// </summary>
 73        public ref char GetPinnableReference()
 74        {
 505975            return ref MemoryMarshal.GetReference(_chars);
 76        }
 77
 78        public ref char this[int index]
 79        {
 80            get
 81            {
 082                Debug.Assert(index < _pos);
 083                return ref _chars[index];
 84            }
 85        }
 86
 87        // ToString() clears the builder, so we need a side-effect free debugger display.
 88        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
 089        private string DebuggerDisplay => AsSpan().ToString();
 90
 91        public override string ToString()
 92        {
 505393            string s = _chars.Slice(0, _pos).ToString();
 505394            Dispose();
 505395            return s;
 96        }
 97
 98        /// <summary>Returns the underlying storage of the builder.</summary>
 099        public Span<char> RawChars => _chars;
 100
 10108101        public ReadOnlySpan<char> AsSpan() => _chars.Slice(0, _pos);
 0102        public ReadOnlySpan<char> AsSpan(int start) => _chars.Slice(start, _pos - start);
 0103        public ReadOnlySpan<char> AsSpan(int start, int length) => _chars.Slice(start, length);
 104
 105        public void Insert(int index, char value, int count)
 106        {
 107            if (_pos > _chars.Length - count)
 108            {
 109                Grow(count);
 110            }
 111
 112            int remaining = _pos - index;
 113            _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
 114            _chars.Slice(index, count).Fill(value);
 115            _pos += count;
 116        }
 117
 118        public void Insert(int index, string? s)
 119        {
 120            if (s == null)
 121            {
 122                return;
 123            }
 124
 125            int count = s.Length;
 126
 127            if (_pos > (_chars.Length - count))
 128            {
 129                Grow(count);
 130            }
 131
 132            int remaining = _pos - index;
 133            _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
 134            s
 135#if !NET
 136                .AsSpan()
 137#endif
 138                .CopyTo(_chars.Slice(index));
 139            _pos += count;
 140        }
 141
 142        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 143        public void Append(char c)
 144        {
 0145            int pos = _pos;
 0146            Span<char> chars = _chars;
 0147            if ((uint)pos < (uint)chars.Length)
 148            {
 0149                chars[pos] = c;
 0150                _pos = pos + 1;
 151            }
 152            else
 153            {
 0154                GrowAndAppend(c);
 155            }
 0156        }
 157
 158        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 159        public void Append(string? s)
 160        {
 0161            if (s == null)
 162            {
 0163                return;
 164            }
 165
 0166            int pos = _pos;
 0167            if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from Numbe
 168            {
 0169                _chars[pos] = s[0];
 0170                _pos = pos + 1;
 171            }
 172            else
 173            {
 0174                AppendSlow(s);
 175            }
 0176        }
 177
 178        private void AppendSlow(string s)
 179        {
 0180            int pos = _pos;
 0181            if (pos > _chars.Length - s.Length)
 182            {
 0183                Grow(s.Length);
 184            }
 185
 0186            s
 0187#if !NET
 0188                .AsSpan()
 0189#endif
 0190                .CopyTo(_chars.Slice(pos));
 0191            _pos += s.Length;
 0192        }
 193
 194        public void Append(char c, int count)
 195        {
 0196            if (_pos > _chars.Length - count)
 197            {
 0198                Grow(count);
 199            }
 200
 0201            Span<char> dst = _chars.Slice(_pos, count);
 0202            for (int i = 0; i < dst.Length; i++)
 203            {
 0204                dst[i] = c;
 205            }
 0206            _pos += count;
 0207        }
 208
 209        public void Append(scoped ReadOnlySpan<char> value)
 210        {
 0211            int pos = _pos;
 0212            if (pos > _chars.Length - value.Length)
 213            {
 0214                Grow(value.Length);
 215            }
 216
 0217            value.CopyTo(_chars.Slice(_pos));
 0218            _pos += value.Length;
 0219        }
 220
 221        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 222        public Span<char> AppendSpan(int length)
 223        {
 0224            int origPos = _pos;
 0225            if (origPos > _chars.Length - length)
 226            {
 0227                Grow(length);
 228            }
 229
 0230            _pos = origPos + length;
 0231            return _chars.Slice(origPos, length);
 232        }
 233
 234        [MethodImpl(MethodImplOptions.NoInlining)]
 235        private void GrowAndAppend(char c)
 236        {
 0237            Grow(1);
 0238            Append(c);
 0239        }
 240
 241        /// <summary>
 242        /// Resize the internal buffer either by doubling current buffer size or
 243        /// by adding <paramref name="additionalCapacityBeyondPos"/> to
 244        /// <see cref="_pos"/> whichever is greater.
 245        /// </summary>
 246        /// <param name="additionalCapacityBeyondPos">
 247        /// Number of chars requested beyond current position.
 248        /// </param>
 249        [MethodImpl(MethodImplOptions.NoInlining)]
 250        private void Grow(int additionalCapacityBeyondPos)
 251        {
 0252            Debug.Assert(additionalCapacityBeyondPos > 0);
 0253            Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos, "Grow called incorrectly, no resize is need
 254
 255            const uint ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength
 256
 257            // Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try
 258            // to double the size if possible, bounding the doubling to not go beyond the max array length.
 0259            int newCapacity = (int)Math.Max(
 0260                (uint)(_pos + additionalCapacityBeyondPos),
 0261                Math.Min((uint)_chars.Length * 2, ArrayMaxLength));
 262
 263            // Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative.
 264            // This could also go negative if the actual required length wraps around.
 0265            char[] poolArray = ArrayPool<char>.Shared.Rent(newCapacity);
 266
 0267            _chars.Slice(0, _pos).CopyTo(poolArray);
 268
 0269            char[]? toReturn = _arrayToReturnToPool;
 0270            _chars = _arrayToReturnToPool = poolArray;
 0271            if (toReturn != null)
 272            {
 0273                ArrayPool<char>.Shared.Return(toReturn);
 274            }
 0275        }
 276
 277        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 278        public void Dispose()
 279        {
 10112280            char[]? toReturn = _arrayToReturnToPool;
 10112281            this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
 10112282            if (toReturn != null)
 283            {
 0284                ArrayPool<char>.Shared.Return(toReturn);
 285            }
 10112286        }
 287    }
 288}
 289

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Text/ValueStringBuilder.AppendFormat.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Diagnostics;
 5using System.Runtime.CompilerServices;
 6using System.Runtime.InteropServices;
 7
 8namespace System.Text
 9{
 10    internal ref partial struct ValueStringBuilder
 11    {
 12        // Copied from StringBuilder, can't be done via generic extension
 13        // as ValueStringBuilder is a ref struct and cannot be used in a generic.
 14        internal void AppendFormatHelper(IFormatProvider? provider, string format, ReadOnlySpan<object?> args)
 15        {
 016            ArgumentNullException.ThrowIfNull(format);
 17
 18            // Undocumented exclusive limits on the range for Argument Hole Index and Argument Hole Alignment.
 19            const int IndexLimit = 1_000_000; // Note:            0 <= ArgIndex < IndexLimit
 20            const int WidthLimit = 1_000_000; // Note:  -WidthLimit <  ArgAlign < WidthLimit
 21
 22            // Query the provider (if one was supplied) for an ICustomFormatter.  If there is one,
 23            // it needs to be used to transform all arguments.
 024            ICustomFormatter? cf = (ICustomFormatter?)provider?.GetFormat(typeof(ICustomFormatter));
 25
 26            // Repeatedly find the next hole and process it.
 027            int pos = 0;
 28            char ch;
 029            while (true)
 30            {
 31                // Skip until either the end of the input or the first unescaped opening brace, whichever comes first.
 32                // Along the way we need to also unescape escaped closing braces.
 33                while (true)
 34                {
 35                    // Find the next brace.  If there isn't one, the remainder of the input is text to be appended, and 
 036                    if ((uint)pos >= (uint)format.Length)
 37                    {
 038                        return;
 39                    }
 40
 041                    ReadOnlySpan<char> remainder = format.AsSpan(pos);
 042                    int countUntilNextBrace = remainder.IndexOfAny('{', '}');
 043                    if (countUntilNextBrace < 0)
 44                    {
 045                        Append(remainder);
 046                        return;
 47                    }
 48
 49                    // Append the text until the brace.
 050                    Append(remainder.Slice(0, countUntilNextBrace));
 051                    pos += countUntilNextBrace;
 52
 53                    // Get the brace.  It must be followed by another character, either a copy of itself in the case of 
 54                    // escaped, or an arbitrary character that's part of the hole in the case of an opening brace.
 055                    char brace = format[pos];
 056                    ch = MoveNext(format, ref pos);
 057                    if (brace == ch)
 58                    {
 059                        Append(ch);
 060                        pos++;
 061                        continue;
 62                    }
 63
 64                    // This wasn't an escape, so it must be an opening brace.
 065                    if (brace != '{')
 66                    {
 067                        ThrowHelper.ThrowFormatInvalidString(pos, ExceptionResource.Format_UnexpectedClosingBrace);
 68                    }
 69
 70                    // Proceed to parse the hole.
 71                    break;
 72                }
 73
 74                // We're now positioned just after the opening brace of an argument hole, which consists of
 75                // an opening brace, an index, an optional width preceded by a comma, and an optional format
 76                // preceded by a colon, with arbitrary amounts of spaces throughout.
 077                int width = 0;
 078                bool leftJustify = false;
 079                ReadOnlySpan<char> itemFormatSpan = default; // used if itemFormat is null
 80
 81                // First up is the index parameter, which is of the form:
 82                //     at least on digit
 83                //     optional any number of spaces
 84                // We've already read the first digit into ch.
 085                Debug.Assert(format[pos - 1] == '{');
 086                Debug.Assert(ch != '{');
 087                int index = ch - '0';
 088                if ((uint)index >= 10u)
 89                {
 090                    ThrowHelper.ThrowFormatInvalidString(pos, ExceptionResource.Format_ExpectedAsciiDigit);
 91                }
 92
 93                // Common case is a single digit index followed by a closing brace.  If it's not a closing brace,
 94                // proceed to finish parsing the full hole format.
 095                ch = MoveNext(format, ref pos);
 096                if (ch != '}')
 97                {
 98                    // Continue consuming optional additional digits.
 099                    while (char.IsAsciiDigit(ch) && index < IndexLimit)
 100                    {
 0101                        index = index * 10 + ch - '0';
 0102                        ch = MoveNext(format, ref pos);
 103                    }
 104
 105                    // Consume optional whitespace.
 0106                    while (ch == ' ')
 107                    {
 0108                        ch = MoveNext(format, ref pos);
 109                    }
 110
 111                    // Parse the optional alignment, which is of the form:
 112                    //     comma
 113                    //     optional any number of spaces
 114                    //     optional -
 115                    //     at least one digit
 116                    //     optional any number of spaces
 0117                    if (ch == ',')
 118                    {
 119                        // Consume optional whitespace.
 120                        do
 121                        {
 0122                            ch = MoveNext(format, ref pos);
 123                        }
 0124                        while (ch == ' ');
 125
 126                        // Consume an optional minus sign indicating left alignment.
 0127                        if (ch == '-')
 128                        {
 0129                            leftJustify = true;
 0130                            ch = MoveNext(format, ref pos);
 131                        }
 132
 133                        // Parse alignment digits. The read character must be a digit.
 0134                        width = ch - '0';
 0135                        if ((uint)width >= 10u)
 136                        {
 0137                            ThrowHelper.ThrowFormatInvalidString(pos, ExceptionResource.Format_ExpectedAsciiDigit);
 138                        }
 0139                        ch = MoveNext(format, ref pos);
 0140                        while (char.IsAsciiDigit(ch) && width < WidthLimit)
 141                        {
 0142                            width = width * 10 + ch - '0';
 0143                            ch = MoveNext(format, ref pos);
 144                        }
 145
 146                        // Consume optional whitespace
 0147                        while (ch == ' ')
 148                        {
 0149                            ch = MoveNext(format, ref pos);
 150                        }
 151                    }
 152
 153                    // The next character needs to either be a closing brace for the end of the hole,
 154                    // or a colon indicating the start of the format.
 0155                    if (ch != '}')
 156                    {
 0157                        if (ch != ':')
 158                        {
 159                            // Unexpected character
 0160                            ThrowHelper.ThrowFormatInvalidString(pos, ExceptionResource.Format_UnclosedFormatItem);
 161                        }
 162
 163                        // Search for the closing brace; everything in between is the format,
 164                        // but opening braces aren't allowed.
 0165                        int startingPos = pos;
 166                        while (true)
 167                        {
 0168                            ch = MoveNext(format, ref pos);
 169
 0170                            if (ch == '}')
 171                            {
 172                                // Argument hole closed
 173                                break;
 174                            }
 175
 0176                            if (ch == '{')
 177                            {
 178                                // Braces inside the argument hole are not supported
 0179                                ThrowHelper.ThrowFormatInvalidString(pos, ExceptionResource.Format_UnclosedFormatItem);
 180                            }
 181                        }
 182
 0183                        startingPos++;
 0184                        itemFormatSpan = format.AsSpan(startingPos, pos - startingPos);
 185                    }
 186                }
 187
 188                // Construct the output for this arg hole.
 0189                Debug.Assert(format[pos] == '}');
 0190                pos++;
 0191                string? s = null;
 0192                string? itemFormat = null;
 193
 0194                if ((uint)index >= (uint)args.Length)
 195                {
 0196                    ThrowHelper.ThrowFormatIndexOutOfRange();
 197                }
 0198                object? arg = args[index];
 199
 0200                if (cf != null)
 201                {
 0202                    if (!itemFormatSpan.IsEmpty)
 203                    {
 0204                        itemFormat = new string(itemFormatSpan);
 205                    }
 206
 0207                    s = cf.Format(itemFormat, arg, provider);
 208                }
 209
 0210                if (s == null)
 211                {
 212                    // If arg is ISpanFormattable and the beginning doesn't need padding,
 213                    // try formatting it into the remaining current chunk.
 0214                    if ((leftJustify || width == 0) &&
 0215                        arg is ISpanFormattable spanFormattableArg &&
 0216                        spanFormattableArg.TryFormat(_chars.Slice(_pos), out int charsWritten, itemFormatSpan, provider)
 217                    {
 0218                        _pos += charsWritten;
 219
 220                        // Pad the end, if needed.
 0221                        if (leftJustify && width > charsWritten)
 222                        {
 0223                            Append(' ', width - charsWritten);
 224                        }
 225
 226                        // Continue to parse other characters.
 0227                        continue;
 228                    }
 229
 230                    // Otherwise, fallback to trying IFormattable or calling ToString.
 0231                    if (arg is IFormattable formattableArg)
 232                    {
 0233                        if (itemFormatSpan.Length != 0)
 234                        {
 0235                            itemFormat ??= new string(itemFormatSpan);
 236                        }
 0237                        s = formattableArg.ToString(itemFormat, provider);
 238                    }
 239                    else
 240                    {
 0241                        s = arg?.ToString();
 242                    }
 243
 0244                    s ??= string.Empty;
 245                }
 246
 247                // Append it to the final output of the Format String.
 0248                if (width <= s.Length)
 249                {
 0250                    Append(s);
 251                }
 0252                else if (leftJustify)
 253                {
 0254                    Append(s);
 0255                    Append(' ', width - s.Length);
 256                }
 257                else
 258                {
 0259                    Append(' ', width - s.Length);
 0260                    Append(s);
 261                }
 262
 263                // Continue parsing the rest of the format string.
 264            }
 265
 266            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 267            static char MoveNext(string format, ref int pos)
 268            {
 0269                pos++;
 0270                if ((uint)pos >= (uint)format.Length)
 271                {
 0272                    ThrowHelper.ThrowFormatInvalidString(pos, ExceptionResource.Format_UnclosedFormatItem);
 273                }
 0274                return format[pos];
 275            }
 276        }
 277    }
 278}
 279