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);