Skip to content

Add support for ImmutableList<T> as the recursive collection #71

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
{
using System.Collections.Immutable;

public interface IRule { }
interface IRule { }

public interface IProjectPropertiesContext { }
interface IProjectPropertiesContext { }

public interface IPropertySheet { }
interface IPropertySheet { }

public class ProjectPropertiesContext : IProjectPropertiesContext
class ProjectPropertiesContext : IProjectPropertiesContext
{
}

Expand All @@ -18,12 +18,12 @@ partial class ProjectTree
[Required]
readonly string caption;
readonly string filePath;
readonly System.Drawing.Image icon;
readonly System.Drawing.Image expandedIcon;
readonly string iconMoniker;
readonly string expandedIconMoniker;
readonly bool visible;
readonly IRule browseObjectProperties;
readonly ImmutableHashSet<string> capabilities;
readonly ImmutableSortedSet<ProjectTree> children;
readonly ImmutableList<ProjectTree> children;
}

[GenerateImmutable(DefineInterface = true, GenerateBuilder = true, DefineWithMethodsPerProperty = true, DefineRootedStruct = true, Delta = true)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,11 +441,17 @@ public void MovingNodeAroundHierarchy()
var moved = root.RemoveDescendent(aa).AddDescendent(aa, ab);

var history = moved.ChangesSince(root);
Assert.Equal(1, history.Count);
Assert.Equal(2, history.Count);

Assert.Equal(ChangeKind.Replaced, history[0].Kind);
Assert.Same(aa, history[0].Before);
Assert.Same(aa, history[0].After);
Assert.Equal(ProjectTreeChangedProperties.Parent, history[0].Changes);

Assert.Equal(ChangeKind.Replaced, history[1].Kind);
Assert.Same(ab, history[1].Before);
Assert.Same(moved.Children[0], history[1].After);
Assert.Equal(ProjectTreeChangedProperties.PositionUnderParent, history[1].Changes);
}

[Fact]
Expand All @@ -460,12 +466,19 @@ public void MovingNodeAroundHierarchyWithOtherChanges()
var moved = root.RemoveDescendent(aa).AddDescendent(aaModified, ab);

var history = moved.ChangesSince(root);
Assert.Equal(1, history.Count);
Assert.Equal(2, history.Count);

Assert.Equal(ChangeKind.Replaced, history[0].Kind);
Assert.Equal(ProjectTreeChangedProperties.Parent | ProjectTreeChangedProperties.Visible, history[0].Changes);
Assert.Same(aa, history[0].Before);
Assert.Same(aaModified, history[0].After);
Assert.Equal(aa.Identity, history[0].Identity);

Assert.Equal(ChangeKind.Replaced, history[1].Kind);
Assert.Equal(ProjectTreeChangedProperties.PositionUnderParent, history[1].Changes);
Assert.Same(ab, history[1].Before);
Assert.Same(moved.Children[0], history[1].After);
Assert.Equal(ab.Identity, history[1].Identity);
}

[Fact]
Expand All @@ -480,17 +493,24 @@ public void MovingNodeAroundHierarchyWithChildAdds()
var moved = root.RemoveDescendent(aa).AddDescendent(aaModified, ab);

var history = moved.ChangesSince(root);
Assert.Equal(2, history.Count);
Assert.Equal(3, history.Count);

Assert.Equal(ChangeKind.Replaced, history[0].Kind);
Assert.Equal(ProjectTreeChangedProperties.Parent, history[0].Changes);
Assert.Same(aa, history[0].Before);
Assert.Same(aaModified, history[0].After);
Assert.Equal(aa.Identity, history[0].Identity);

Assert.Equal(ChangeKind.Added, history[1].Kind);
Assert.Same(aaModified.Children[0], history[1].After);
Assert.Null(history[1].Before);
Assert.Equal(aaModified.Children[0].Identity, history[1].Identity);
Assert.Equal(ChangeKind.Replaced, history[1].Kind);
Assert.Equal(ProjectTreeChangedProperties.PositionUnderParent, history[1].Changes);
Assert.Same(ab, history[1].Before);
Assert.Same(moved.Children[0], history[1].After);
Assert.Equal(ab.Identity, history[1].Identity);

Assert.Equal(ChangeKind.Added, history[2].Kind);
Assert.Same(aaModified.Children[0], history[2].After);
Assert.Null(history[2].Before);
Assert.Equal(aaModified.Children[0].Identity, history[2].Identity);
}

[Fact]
Expand All @@ -506,7 +526,8 @@ public void MovingNodeAroundHierarchyWithChildRemoves()
var moved = root.RemoveDescendent(aa).AddDescendent(aaModified, ab);

var history = moved.ChangesSince(root);
Assert.Equal(2, history.Count);
Assert.Equal(3, history.Count);

Assert.Equal(ChangeKind.Removed, history[0].Kind);
Assert.Same(aa.Children[0], history[0].Before);
Assert.Null(history[0].After);
Expand All @@ -517,6 +538,12 @@ public void MovingNodeAroundHierarchyWithChildRemoves()
Assert.Same(aa, history[1].Before);
Assert.Same(aaModified, history[1].After);
Assert.Equal(aa.Identity, history[1].Identity);

Assert.Equal(ChangeKind.Replaced, history[2].Kind);
Assert.Equal(ProjectTreeChangedProperties.PositionUnderParent, history[2].Changes);
Assert.Same(ab, history[2].Before);
Assert.Same(moved.Children[0], history[2].After);
Assert.Equal(ab.Identity, history[2].Identity);
}

[Fact]
Expand All @@ -527,7 +554,8 @@ public void RepositioningNodeWithinParentsChildren()
aa = ProjectTree.Create("AA"),
ab = ProjectTree.Create("AB"));
var ac = aa.WithCaption("AC");
var modified = root.ReplaceDescendent(aa, ac);
// TODO: we should generate a method for ordered collections for reordering.
var modified = root.WithChildren(root.Children.Remove(aa).Insert(1, ac));

var history = modified.ChangesSince(root);
Assert.Equal(1, history.Count);
Expand All @@ -536,5 +564,32 @@ public void RepositioningNodeWithinParentsChildren()
Assert.Same(aa, history[0].Before);
Assert.Same(ac, history[0].After);
}

[Fact(Skip = "Not yet passing")]
public void ReorderChildrenInOrderedList()
{
ProjectTree aa, ab, ac, ad, ae;
var original = ProjectTree.Create("A").WithChildren(
aa = ProjectTree.Create("AA"),
ab = ProjectTree.Create("AB"),
ac = ProjectTree.Create("AC"),
ad = ProjectTree.Create("AD"),
ae = ProjectTree.Create("AE"));
var reordered = original.RemoveChild(ac).AddChild(ac); // move AC to bottom.
Assert.Equal(new string[] { "AA", "AB", "AD", "AE", "AC" }, reordered.Children.Select(c => c.Caption));

var changes = reordered.ChangesSince(original);
Assert.NotEmpty(changes);

// Now move it back to its original position.
var restored = reordered.WithChildren(
reordered.Children.Remove(ac).Insert(2, ac));
Assert.Equal(new string[] { "AA", "AB", "AC", "AD", "AE" }, restored.Children.Select(c => c.Caption));

changes = restored.ChangesSince(reordered);
Assert.NotEmpty(changes);

Assert.Empty(restored.ChangesSince(original));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ public abstract class ProjectTreeNodeTestBase : IDisposable

private int nodeCounter;

internal ImmutableSortedSet<ProjectTree> Children { get; set; }
internal ImmutableList<ProjectTree> Children { get; set; }

public ProjectTreeNodeTestBase()
{
this.nodeCounter = 0;
this.Children = ImmutableSortedSet.Create(ProjectTreeSort.Default);
this.Children = ImmutableList.Create<ProjectTree>();
}

public void Dispose()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static IReadOnlyList<DiffGram> GetDelta(ProjectTree before, ProjectTree a

static partial void CreateDefaultTemplate(ref ProjectTree.Template template)
{
template.Children = ImmutableSortedSet.Create(ProjectTreeSort.Default);
template.Children = ImmutableList.Create<ProjectTree>();
template.Capabilities = ImmutableHashSet.Create<string>(StringComparer.OrdinalIgnoreCase);
template.Visible = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,10 +208,13 @@ private MethodDeclarationSyntax CreateParamsElementArrayMethod(MetaField field,
var lambdaParameter = SyntaxFactory.Parameter(SyntaxFactory.Identifier("v"));
var argument = passThroughChildSync
? (ExpressionSyntax)Syntax.EnumerableExtension(
SyntaxFactory.IdentifierName(nameof(Enumerable.Select)),
ValuesParameterName,
SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(
SyntaxFactory.Argument(Syntax.ThisDot(SyncImmediateChildToCurrentVersionMethodName)))))
SyntaxFactory.IdentifierName(nameof(Enumerable.ToList)),
Syntax.EnumerableExtension(
SyntaxFactory.IdentifierName(nameof(Enumerable.Select)),
ValuesParameterName,
SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(
SyntaxFactory.Argument(Syntax.ThisDot(SyncImmediateChildToCurrentVersionMethodName))))),
SyntaxFactory.ArgumentList())
: ValuesParameterName;

paramsArrayMethod = this.AddMethodBody(
Expand Down
48 changes: 31 additions & 17 deletions src/ImmutableObjectGraph.Generation/CodeGen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1289,24 +1289,9 @@ public MetaType RootAncestorOrThisType
}
}

public bool ChildrenAreSorted
{
get
{
// Not very precise, but it does the job for now.
return this.RecursiveParent.RecursiveField.Type.Name == nameof(ImmutableSortedSet<int>);
}
}
public bool ChildrenAreSorted => this.RecursiveParent.RecursiveField.IsCollectionSorted;

public bool ChildrenAreOrdered
{
get
{
// Not very precise, but it does the job for now.
var namedType = this.RecursiveParent.RecursiveField.Type as INamedTypeSymbol;
return namedType != null && namedType.AllInterfaces.Any(iface => iface.Name == nameof(IReadOnlyList<int>));
}
}
public bool ChildrenAreOrdered => this.RecursiveParent.RecursiveField.IsCollectionOrdered;

public IEnumerable<MetaField> GetFieldsBeyond(MetaType ancestor)
{
Expand Down Expand Up @@ -1435,6 +1420,35 @@ public MetaType TypeAsGeneratedImmutable

public bool IsDictionary => IsDictionaryType(this.Symbol.Type);

/// <summary>
/// Gets a value indicating whether the elements in the collection have a defined order.
/// This may be because it is sorted, or because it is documented to retain the order
/// in which the elements were inserted (e.g. a list).
/// </summary>
public bool IsCollectionOrdered
{
get
{
if (this.IsCollection)
{
// Not very precise, but it does the job for now.
var namedType = this.Type as INamedTypeSymbol;
return namedType?.AllInterfaces.Any(iface => iface.Name == nameof(IReadOnlyList<int>)) ?? false;
}

return false;
}
}

public bool IsCollectionSorted
{
get
{
// Not very precise, but it does the job for now.
return this.Type.Name == nameof(ImmutableSortedSet<int>);
}
}

public MetaType DeclaringType
{
get { return new MetaType(this.metaType.Generator, this.Symbol.ContainingType); }
Expand Down