diff --git a/src/ImmutableObjectGraph.Generation.Tests/CodeGenTests.cs b/src/ImmutableObjectGraph.Generation.Tests/CodeGenTests.cs index 1190775..9df49cd 100644 --- a/src/ImmutableObjectGraph.Generation.Tests/CodeGenTests.cs +++ b/src/ImmutableObjectGraph.Generation.Tests/CodeGenTests.cs @@ -109,6 +109,13 @@ public async Task OneScalarFieldWithBuilder_HasCreateBuilderMethod() Assert.True(result.DeclaredMethods.Any(m => m.Name == "CreateBuilder" && m.Parameters.Length == 0 && m.IsStatic)); } + [Fact] + public async Task OneScalarFieldWithBuilder_HasCopyIntoMethod() + { + var result = await this.GenerateFromStreamAsync("OneScalarFieldWithBuilder"); + Assert.True(result.DeclaredMethods.Any(m => m.Name == "CopyInto" && m.Parameters.Length == 1 && !m.IsStatic)); + } + [Fact] public async Task OneScalarFieldWithBuilder_BuilderHasMutableProperties() { diff --git a/src/ImmutableObjectGraph.Generation.Tests/TestSources/Person.BuilderTests.cs b/src/ImmutableObjectGraph.Generation.Tests/TestSources/Person.BuilderTests.cs index 50dd8bb..7b198d4 100644 --- a/src/ImmutableObjectGraph.Generation.Tests/TestSources/Person.BuilderTests.cs +++ b/src/ImmutableObjectGraph.Generation.Tests/TestSources/Person.BuilderTests.cs @@ -43,6 +43,49 @@ public void MutablePropertiesRetainChanges() Assert.Equal(8, personBuilder.Age); } + [Fact] + public void CopyIntoWorks() + { + var bill = Person.Create("bill", age: 10); + var sally = Person.Create("sally", age: 12); + var personBuilder = bill.ToBuilder(); + + personBuilder.Name = "billy"; + personBuilder.Age = 8; + + Assert.Equal("billy", personBuilder.Name); + Assert.Equal(8, personBuilder.Age); + + personBuilder.CopyInto(sally); + + Assert.Equal("sally", personBuilder.Name); + Assert.Equal(12, personBuilder.Age); + Assert.Equal(null, personBuilder.Watch); + } + + [Fact] + public void CopyInto2Works() + { + var bill = Person.Create("bill", age: 10, watch: Watch.Create()); + var watch = Watch.Create(color:"blue", size:67); + var sally = Person.Create("sally", age: 12, watch: watch); + var personBuilder = bill.ToBuilder(); + + personBuilder.Name = "billy"; + personBuilder.Age = 8; + + Assert.Equal("billy", personBuilder.Name); + Assert.Equal(8, personBuilder.Age); + + personBuilder.CopyInto(sally); + + Assert.Equal("sally", personBuilder.Name); + Assert.Equal(12, personBuilder.Age); + Assert.NotNull(personBuilder.Watch); + + Assert.Equal(personBuilder.ToImmutable().Watch, watch); + } + [Fact] public void ToImmutableReturnsSimilarObject() { diff --git a/src/ImmutableObjectGraph.Generation/CodeGen+BuilderGen.cs b/src/ImmutableObjectGraph.Generation/CodeGen+BuilderGen.cs index 9816814..3cc79db 100644 --- a/src/ImmutableObjectGraph.Generation/CodeGen+BuilderGen.cs +++ b/src/ImmutableObjectGraph.Generation/CodeGen+BuilderGen.cs @@ -73,6 +73,7 @@ protected override void GenerateCore() .AddBaseListTypes(SyntaxFactory.SimpleBaseType(INotifyPropertyChanged)); builderMembers.Add(this.CreatePropertyChangedEvent()); builderMembers.Add(this.CreateOnPropertyChangedMethod()); + builderMembers.Add(this.CreateCopyIntoMethod()); } builderType = builderType @@ -80,6 +81,227 @@ protected override void GenerateCore() this.innerMembers.Add(builderType); } + public static string FirstCharToUpper(string input) + { + if (String.IsNullOrEmpty(input)) + throw new ArgumentException("ARGH!"); + return input.First().ToString().ToUpper() + String.Join("", input.Skip(1)); + } + protected MethodDeclarationSyntax CreateCopyIntoMethod() + { + + var assignments = this.generator.applyToMetaType.LocalFields + .Select + (field => AssignmentFromOther(field)).ToArray(); + + return SyntaxFactory.MethodDeclaration + ( + SyntaxFactory.PredefinedType + ( + SyntaxFactory.Token(SyntaxKind.VoidKeyword)), + SyntaxFactory.Identifier("CopyInto")) + .WithModifiers + ( + SyntaxFactory.TokenList + ( + SyntaxFactory.Token(SyntaxKind.PublicKeyword))) + .WithParameterList + ( + SyntaxFactory.ParameterList + ( + SyntaxFactory.SingletonSeparatedList + ( + SyntaxFactory.Parameter + ( + SyntaxFactory.Identifier("other")) + .WithType + ( + SyntaxFactory.IdentifierName((this.generator.applyTo.Identifier)))))) + .WithBody + ( + SyntaxFactory.Block + (assignments)); + } + + private static StatementSyntax AssignmentFromOther(MetaField field) + { + return field.IsGeneratedImmutableType ? (StatementSyntax) CopyIntoAssignment(field) : SyntaxFactory.ExpressionStatement(SimpleAssignmentFromOther(field)); + } + + private static StatementSyntax CopyIntoAssignment(MetaField field) + { + var capFieldName = FirstCharToUpper(field.Name); + + return SyntaxFactory.IfStatement + ( + SyntaxFactory.MemberAccessExpression + ( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.MemberAccessExpression + ( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.ThisExpression(), + SyntaxFactory.IdentifierName(field.Name)), + SyntaxFactory.IdentifierName("IsDefined")), + SyntaxFactory.Block + ( + SyntaxFactory.SingletonList + ( + SyntaxFactory.ExpressionStatement + ( + SyntaxFactory.InvocationExpression + ( + SyntaxFactory.MemberAccessExpression + ( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.MemberAccessExpression + ( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.ThisExpression(), + SyntaxFactory.IdentifierName(capFieldName)), + SyntaxFactory.IdentifierName("CopyInto"))) + .WithArgumentList + ( + SyntaxFactory.ArgumentList + ( + SyntaxFactory.SingletonSeparatedList + ( + SyntaxFactory.Argument + ( + SyntaxFactory.MemberAccessExpression + ( + SyntaxKind + .SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName + ("other"), + SyntaxFactory.IdentifierName + (capFieldName)))))))))) + .WithElse + ( + SyntaxFactory.ElseClause( + SyntaxFactory.Block( + SyntaxFactory.LocalDeclarationStatement( + SyntaxFactory.VariableDeclaration( + SyntaxFactory.IdentifierName("var")) + .WithVariables( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.VariableDeclarator( + SyntaxFactory.Identifier("builder")) + .WithInitializer( + SyntaxFactory.EqualsValueClause( + SyntaxFactory.ConditionalAccessExpression( + SyntaxFactory.IdentifierName("other"), + SyntaxFactory.ConditionalAccessExpression( + SyntaxFactory.MemberBindingExpression( + SyntaxFactory.IdentifierName(capFieldName)), + SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberBindingExpression( + SyntaxFactory.IdentifierName("ToBuilder")))))))))), + SyntaxFactory.IfStatement + ( + SyntaxFactory.BinaryExpression + ( + SyntaxKind.NotEqualsExpression, + SyntaxFactory.IdentifierName("builder"), + SyntaxFactory.LiteralExpression + ( + SyntaxKind.NullLiteralExpression)), + SyntaxFactory.ExpressionStatement + ( + SyntaxFactory.AssignmentExpression + ( + SyntaxKind.SimpleAssignmentExpression, + SyntaxFactory.MemberAccessExpression + ( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.ThisExpression(), + SyntaxFactory.IdentifierName(field.Name)), + SyntaxFactory.InvocationExpression + ( + SyntaxFactory.MemberAccessExpression + ( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.MemberAccessExpression + ( + SyntaxKind + .SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName + ("ImmutableObjectGraph"), + SyntaxFactory.IdentifierName + ("Optional")), + SyntaxFactory.IdentifierName("For"))) + .WithArgumentList + ( + SyntaxFactory.ArgumentList + ( + SyntaxFactory + .SingletonSeparatedList( + SyntaxFactory.Argument + ( + SyntaxFactory + .IdentifierName + ("builder")))))))) + .WithElse + ( + SyntaxFactory.ElseClause + ( + SyntaxFactory.ExpressionStatement + ( + SyntaxFactory.AssignmentExpression + ( + SyntaxKind.SimpleAssignmentExpression, + SyntaxFactory.MemberAccessExpression + ( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.ThisExpression(), + SyntaxFactory.IdentifierName(field.Name)), + SyntaxFactory.ObjectCreationExpression + ( + SyntaxFactory.QualifiedName + ( + SyntaxFactory.IdentifierName + ("ImmutableObjectGraph"), + SyntaxFactory.GenericName + ( + SyntaxFactory.Identifier + ("Optional")) + .WithTypeArgumentList + ( + SyntaxFactory + .TypeArgumentList + ( + SyntaxFactory + .SingletonSeparatedList + + ( + SyntaxFactory + .IdentifierName + (field.Type.Name + "" + + + ".Builder")))))) + .WithArgumentList + ( + SyntaxFactory.ArgumentList())))))))) + .NormalizeWhitespace(); + + } + + private static ExpressionSyntax SimpleAssignmentFromOther(MetaField field) + { + return (ExpressionSyntax) SyntaxFactory.AssignmentExpression + ( + SyntaxKind.SimpleAssignmentExpression, + SyntaxFactory.MemberAccessExpression + ( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.ThisExpression(), + SyntaxFactory.IdentifierName(field.Name)), + SyntaxFactory.MemberAccessExpression + ( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName("other"), + SyntaxFactory.IdentifierName(field.Name))); + } protected MethodDeclarationSyntax CreateToBuilderMethod() { @@ -102,6 +324,7 @@ protected MethodDeclarationSyntax CreateToBuilderMethod() return method; } + protected MethodDeclarationSyntax CreateCreateBuilderMethod() { var method = SyntaxFactory.MethodDeclaration( diff --git a/src/ImmutableObjectGraph/Optional.cs b/src/ImmutableObjectGraph/Optional.cs index 79ed461..ddc2639 100644 --- a/src/ImmutableObjectGraph/Optional.cs +++ b/src/ImmutableObjectGraph/Optional.cs @@ -1,4 +1,6 @@ -namespace ImmutableObjectGraph +using System.Runtime.InteropServices.WindowsRuntime; + +namespace ImmutableObjectGraph { using System.Diagnostics; @@ -7,5 +9,7 @@ public static class Optional { public static Optional For(T value) { return value; } + + public static Optional Missing()=>new Optional(); } } diff --git a/src/RoslynDemo/Program.cs b/src/RoslynDemo/Program.cs index e3f99dc..2a8dbee 100644 --- a/src/RoslynDemo/Program.cs +++ b/src/RoslynDemo/Program.cs @@ -25,6 +25,7 @@ static void Main(string[] args) messageBuilder.Author.Name = "And again, myself"; messageBuilder.Author.Email = "oh@so.mutable"; messageBuilder.To.Add(i); + messageBuilder.CopyInto(message); var updatedMessage = messageBuilder.ToImmutable(); }