diff --git a/Src/CSharpier.Tests/CSharpier.Tests.csproj b/Src/CSharpier.Tests/CSharpier.Tests.csproj
index 1fda1c267..9ad4367ee 100644
--- a/Src/CSharpier.Tests/CSharpier.Tests.csproj
+++ b/Src/CSharpier.Tests/CSharpier.Tests.csproj
@@ -7,6 +7,7 @@
True
direct
+ 13
diff --git a/Src/CSharpier/DocPrinter/DocFitter.cs b/Src/CSharpier/DocPrinter/DocFitter.cs
index 286a41e8c..00db91bf9 100644
--- a/Src/CSharpier/DocPrinter/DocFitter.cs
+++ b/Src/CSharpier/DocPrinter/DocFitter.cs
@@ -63,8 +63,8 @@ void Push(Doc doc, PrintMode printMode, Indent indent)
case Region:
return false;
case Concat concat:
- for (var i = concat.Contents.Count - 1; i >= 0; i--)
- Push(concat.Contents[i], currentMode, currentIndent);
+ for (var i = concat.Count - 1; i >= 0; i--)
+ Push(concat[i], currentMode, currentIndent);
break;
case IndentDoc indent:
Push(indent.Contents, currentMode, indenter.IncreaseIndent(currentIndent));
diff --git a/Src/CSharpier/DocPrinter/DocPrinter.cs b/Src/CSharpier/DocPrinter/DocPrinter.cs
index e1fa39663..e8630fd49 100644
--- a/Src/CSharpier/DocPrinter/DocPrinter.cs
+++ b/Src/CSharpier/DocPrinter/DocPrinter.cs
@@ -85,9 +85,9 @@ private void ProcessNextCommand()
}
else if (doc is Concat concat)
{
- for (var x = concat.Contents.Count - 1; x >= 0; x--)
+ for (var x = concat.Count - 1; x >= 0; x--)
{
- this.Push(concat.Contents[x], mode, indent);
+ this.Push(concat[x], mode, indent);
}
}
else if (doc is IndentDoc indentDoc)
diff --git a/Src/CSharpier/DocPrinter/PropagateBreaks.cs b/Src/CSharpier/DocPrinter/PropagateBreaks.cs
index 554f1df8b..859f349f8 100644
--- a/Src/CSharpier/DocPrinter/PropagateBreaks.cs
+++ b/Src/CSharpier/DocPrinter/PropagateBreaks.cs
@@ -104,15 +104,14 @@ void OnExit(Doc doc)
if (doc is Concat concat)
{
// push onto stack in reverse order so they are processed in the original order
- for (var x = concat.Contents.Count - 1; x >= 0; --x)
+ for (var x = concat.Count - 1; x >= 0; --x)
{
- if (forceFlat > 0 && concat.Contents[x] is LineDoc { IsLiteral: false } lineDoc)
+ if (forceFlat > 0 && concat[x] is LineDoc { IsLiteral: false } lineDoc)
{
- concat.Contents[x] =
- lineDoc.Type == LineDoc.LineType.Soft ? string.Empty : " ";
+ concat[x] = lineDoc.Type == LineDoc.LineType.Soft ? string.Empty : " ";
}
- docsStack.Push(concat.Contents[x]);
+ docsStack.Push(concat[x]);
}
}
else if (doc is IfBreak ifBreak)
diff --git a/Src/CSharpier/DocSerializer.cs b/Src/CSharpier/DocSerializer.cs
index 60090cfe0..aa266829f 100644
--- a/Src/CSharpier/DocSerializer.cs
+++ b/Src/CSharpier/DocSerializer.cs
@@ -127,17 +127,17 @@ void AppendNextIndent()
{
AppendIndent();
result.Append("Doc.Concat(");
- if (concat.Contents.Count > 0)
+ if (concat.Count > 0)
{
result.AppendLine();
}
}
- for (var x = 0; x < concat.Contents.Count; x++)
+ for (var x = 0; x < concat.Count; x++)
{
- Serialize(concat.Contents[x], result, skipConcat ? indent : indent + 1);
+ Serialize(concat[x], result, skipConcat ? indent : indent + 1);
- if (x < concat.Contents.Count - 1)
+ if (x < concat.Count - 1)
{
result.AppendLine(",");
}
diff --git a/Src/CSharpier/DocTypes/Concat.WithFourChildren.cs b/Src/CSharpier/DocTypes/Concat.WithFourChildren.cs
new file mode 100644
index 000000000..8d9c0354d
--- /dev/null
+++ b/Src/CSharpier/DocTypes/Concat.WithFourChildren.cs
@@ -0,0 +1,74 @@
+namespace CSharpier.DocTypes;
+
+internal abstract partial class Concat
+{
+ internal sealed class WithFourChildren(Doc child0, Doc child1, Doc child2, Doc child3) : Concat
+ {
+ private Doc _child0 = child0;
+ private Doc _child1 = child1;
+ private Doc _child2 = child2;
+ private Doc _child3 = child3;
+
+ public override int Count => 4;
+
+ public override Doc this[int index]
+ {
+ get =>
+ index switch
+ {
+ 0 => _child0,
+ 1 => _child1,
+ 2 => _child2,
+ 3 => _child3,
+ _ => throw new IndexOutOfRangeException(nameof(index)),
+ };
+ set
+ {
+ switch (index)
+ {
+ case 0:
+ _child0 = value;
+ break;
+ case 1:
+ _child1 = value;
+ break;
+ case 2:
+ _child2 = value;
+ break;
+ case 3:
+ _child3 = value;
+ break;
+ default:
+ throw new IndexOutOfRangeException(nameof(index));
+ }
+ }
+ }
+
+ public override void RemoveAt(int index)
+ {
+ switch (index)
+ {
+ case 0:
+ _child0 = _child1;
+ _child1 = _child2;
+ _child2 = _child3;
+ _child3 = Doc.Null;
+ break;
+ case 1:
+ _child1 = _child2;
+ _child2 = _child3;
+ _child3 = Doc.Null;
+ break;
+ case 2:
+ _child2 = _child3;
+ _child3 = Doc.Null;
+ break;
+ case 3:
+ _child3 = Doc.Null;
+ break;
+ default:
+ throw new IndexOutOfRangeException(nameof(index));
+ }
+ }
+ }
+}
diff --git a/Src/CSharpier/DocTypes/Concat.WithManyChildren.cs b/Src/CSharpier/DocTypes/Concat.WithManyChildren.cs
new file mode 100644
index 000000000..8df661e6b
--- /dev/null
+++ b/Src/CSharpier/DocTypes/Concat.WithManyChildren.cs
@@ -0,0 +1,17 @@
+namespace CSharpier.DocTypes;
+
+internal abstract partial class Concat
+{
+ internal sealed class WithManyChildren(IList content) : Concat
+ {
+ public override int Count => content.Count;
+
+ public override Doc this[int index]
+ {
+ get => content[index];
+ set => content[index] = value;
+ }
+
+ public override void RemoveAt(int index) => content.RemoveAt(index);
+ }
+}
diff --git a/Src/CSharpier/DocTypes/Concat.WithThreeChildren.cs b/Src/CSharpier/DocTypes/Concat.WithThreeChildren.cs
new file mode 100644
index 000000000..3075c51a4
--- /dev/null
+++ b/Src/CSharpier/DocTypes/Concat.WithThreeChildren.cs
@@ -0,0 +1,63 @@
+namespace CSharpier.DocTypes;
+
+internal abstract partial class Concat
+{
+ internal sealed class WithThreeChildren(Doc child0, Doc child1, Doc child2) : Concat
+ {
+ private Doc _child0 = child0;
+ private Doc _child1 = child1;
+ private Doc _child2 = child2;
+
+ public override int Count => 3;
+
+ public override Doc this[int index]
+ {
+ get =>
+ index switch
+ {
+ 0 => _child0,
+ 1 => _child1,
+ 2 => _child2,
+ _ => throw new IndexOutOfRangeException(nameof(index)),
+ };
+ set
+ {
+ switch (index)
+ {
+ case 0:
+ _child0 = value;
+ break;
+ case 1:
+ _child1 = value;
+ break;
+ case 2:
+ _child2 = value;
+ break;
+ default:
+ throw new IndexOutOfRangeException(nameof(index));
+ }
+ }
+ }
+
+ public override void RemoveAt(int index)
+ {
+ switch (index)
+ {
+ case 0:
+ _child0 = _child1;
+ _child1 = _child2;
+ _child2 = Doc.Null;
+ break;
+ case 1:
+ _child1 = _child2;
+ _child2 = Doc.Null;
+ break;
+ case 2:
+ _child2 = Doc.Null;
+ break;
+ default:
+ throw new IndexOutOfRangeException(nameof(index));
+ }
+ }
+ }
+}
diff --git a/Src/CSharpier/DocTypes/Concat.WithTwoChildren.cs b/Src/CSharpier/DocTypes/Concat.WithTwoChildren.cs
new file mode 100644
index 000000000..90ee222df
--- /dev/null
+++ b/Src/CSharpier/DocTypes/Concat.WithTwoChildren.cs
@@ -0,0 +1,52 @@
+namespace CSharpier.DocTypes;
+
+internal abstract partial class Concat
+{
+ internal sealed class WithTwoChildren(Doc child0, Doc child1) : Concat
+ {
+ private Doc _child0 = child0;
+ private Doc _child1 = child1;
+ public override int Count => 2;
+
+ public override Doc this[int index]
+ {
+ get =>
+ index switch
+ {
+ 0 => _child0,
+ 1 => _child1,
+ _ => throw new IndexOutOfRangeException(nameof(index)),
+ };
+ set
+ {
+ switch (index)
+ {
+ case 0:
+ _child0 = value;
+ break;
+ case 1:
+ _child1 = value;
+ break;
+ default:
+ throw new IndexOutOfRangeException(nameof(index));
+ }
+ }
+ }
+
+ public override void RemoveAt(int index)
+ {
+ switch (index)
+ {
+ case 0:
+ _child0 = _child1;
+ _child1 = Doc.Null;
+ break;
+ case 1:
+ _child1 = Doc.Null;
+ break;
+ default:
+ throw new IndexOutOfRangeException(nameof(index));
+ }
+ }
+ }
+}
diff --git a/Src/CSharpier/DocTypes/Concat.cs b/Src/CSharpier/DocTypes/Concat.cs
index b937129c1..d6c35df91 100644
--- a/Src/CSharpier/DocTypes/Concat.cs
+++ b/Src/CSharpier/DocTypes/Concat.cs
@@ -1,6 +1,38 @@
namespace CSharpier.DocTypes;
-internal sealed class Concat(IList contents) : Doc
+internal abstract partial class Concat : Doc
{
- public IList Contents { get; set; } = contents;
+ public abstract int Count { get; }
+
+ public abstract Doc this[int index] { get; set; }
+
+ public abstract void RemoveAt(int index);
+
+ public bool Any(Func predicate)
+ {
+ for (var i = 0; i < Count; i++)
+ {
+ if (predicate(this[i]))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static Concat Create(IList collection) => new WithManyChildren(collection);
+
+ public static Doc Create(ReadOnlySpan collection)
+ {
+ return collection.Length switch
+ {
+ 0 => Doc.Null,
+ 1 => collection[0],
+ 2 => new WithTwoChildren(collection[0], collection[1]),
+ 3 => new WithThreeChildren(collection[0], collection[1], collection[2]),
+ 4 => new WithFourChildren(collection[0], collection[1], collection[2], collection[3]),
+ _ => new WithManyChildren(collection.ToArray()),
+ };
+ }
}
diff --git a/Src/CSharpier/DocTypes/Doc.cs b/Src/CSharpier/DocTypes/Doc.cs
index f85ee631c..86d3e4ce4 100644
--- a/Src/CSharpier/DocTypes/Doc.cs
+++ b/Src/CSharpier/DocTypes/Doc.cs
@@ -49,12 +49,14 @@ public static TrailingComment TrailingComment(string comment, CommentType commen
new() { Type = commentType, Comment = comment };
public static Doc Concat(List contents) =>
- contents.Count == 1 ? contents[0] : new Concat(contents);
+ contents.Count == 1 ? contents[0] : DocTypes.Concat.Create(contents);
// prevents allocating an array if there is only a single parameter
public static Doc Concat(Doc contents) => contents;
- public static Doc Concat(params Doc[] contents) => new Concat(contents);
+ public static Doc Concat(Doc[] contents) => DocTypes.Concat.Create((IList)contents);
+
+ public static Doc Concat(params ReadOnlySpan contents) => DocTypes.Concat.Create(contents);
public static Doc Join(Doc separator, IEnumerable array)
{
@@ -83,7 +85,7 @@ public static Doc Join(Doc separator, IEnumerable array)
public static ForceFlat ForceFlat(List contents) =>
new() { Contents = contents.Count == 0 ? contents[0] : Concat(contents) };
- public static ForceFlat ForceFlat(params Doc[] contents) =>
+ public static ForceFlat ForceFlat(params ReadOnlySpan contents) =>
new() { Contents = contents.Length == 0 ? contents[0] : Concat(contents) };
public static Group Group(List contents) =>
@@ -104,7 +106,7 @@ public static Group GroupWithId(string groupId, Doc contents)
return group;
}
- public static Group GroupWithId(string groupId, params Doc[] contents)
+ public static Group GroupWithId(string groupId, params ReadOnlySpan contents)
{
var group = Group(contents);
group.GroupId = groupId;
@@ -114,12 +116,12 @@ public static Group GroupWithId(string groupId, params Doc[] contents)
// prevents allocating an array if there is only a single parameter
public static Group Group(Doc contents) => new() { Contents = contents };
- public static Group Group(params Doc[] contents) => new() { Contents = Concat(contents) };
+ public static Group Group(params ReadOnlySpan contents) => new() { Contents = Concat(contents) };
// prevents allocating an array if there is only a single parameter
public static IndentDoc Indent(Doc contents) => new() { Contents = contents };
- public static IndentDoc Indent(params Doc[] contents) => new() { Contents = Concat(contents) };
+ public static IndentDoc Indent(params ReadOnlySpan contents) => new() { Contents = Concat(contents) };
public static IndentDoc Indent(List contents) => new() { Contents = Concat(contents) };
diff --git a/Src/CSharpier/DocTypes/DocUtilities.cs b/Src/CSharpier/DocTypes/DocUtilities.cs
index 8338eb0ef..e6123700d 100644
--- a/Src/CSharpier/DocTypes/DocUtilities.cs
+++ b/Src/CSharpier/DocTypes/DocUtilities.cs
@@ -14,7 +14,7 @@ public static bool ContainsBreak(Doc doc)
return doc switch
{
IHasContents hasContents => ContainsBreak(hasContents.Contents),
- Concat concat => concat.Contents.Any(ContainsBreak),
+ Concat concat => concat.Any(ContainsBreak),
IfBreak ifBreak => ContainsBreak(ifBreak.FlatContents),
_ => false,
};
@@ -65,6 +65,33 @@ private static void RemoveInitialDoubleHardLine(IList docs, ref bool remove
}
}
+ private static void RemoveInitialDoubleHardLineConcat(Concat docs, ref bool removeNextHardLine)
+ {
+ var x = 0;
+ while (x < docs.Count)
+ {
+ var doc = docs[x];
+
+ if (doc is HardLine)
+ {
+ if (removeNextHardLine)
+ {
+ docs[x] = Doc.Null;
+ return;
+ }
+
+ removeNextHardLine = true;
+ }
+ else
+ {
+ RemoveInitialDoubleHardLine(doc, ref removeNextHardLine);
+ return;
+ }
+
+ x++;
+ }
+ }
+
public static Doc RemoveInitialDoubleHardLine(Doc doc)
{
var removeNextHardLine = false;
@@ -112,7 +139,7 @@ private static void RemoveInitialDoubleHardLine(Doc doc, ref bool removeNextHard
return;
}
case Concat concat:
- RemoveInitialDoubleHardLine(concat.Contents, ref removeNextHardLine);
+ RemoveInitialDoubleHardLineConcat(concat, ref removeNextHardLine);
return;
}
}
diff --git a/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/CompilationUnit.cs b/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/CompilationUnit.cs
index 2dc9cc7ba..8b91be0a9 100644
--- a/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/CompilationUnit.cs
+++ b/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/CompilationUnit.cs
@@ -18,16 +18,16 @@ public static Doc Print(CompilationUnitSyntax node, PrintingContext context)
{
// really ugly code to prevent a comment at the end of a file from continually inserting new blank lines
if (
- finalTrivia is Concat { Contents.Count: > 1 } list
+ finalTrivia is Concat { Count: > 1 } list
&& docs.Count > 0
- && docs[^1] is Concat { Contents.Count: > 1 } previousList
- && previousList.Contents[^1] is HardLine
- && previousList.Contents[^2] is HardLine
+ && docs[^1] is Concat { Count: > 1 } previousList
+ && previousList[^1] is HardLine
+ && previousList[^2] is HardLine
)
{
- while (list.Contents[0] is HardLine { SkipBreakIfFirstInGroup: true })
+ while (list[0] is HardLine { SkipBreakIfFirstInGroup: true })
{
- list.Contents.RemoveAt(0);
+ list.RemoveAt(0);
}
docs.Add(finalTrivia);