From 26508051a49019f8ee524ca6477270ff0bf2ec6f Mon Sep 17 00:00:00 2001 From: William Chargin Date: Mon, 26 Mar 2018 13:58:12 -0700 Subject: [PATCH] Add a covariant `copy` method on `Graph` (#109) Summary: Clients of `Graph` that wish to treat the graph as immutable will benefit from a `copy` method. We should provide it on `Graph` instead of asking clients to reimplement it because it affords us the opportunity to get the type signature right: in particular, copying should allow upcasting of the type parameters, even though `Graph` itself is invariant. Paired with @dandelionmane. Test Plan: Unit tests added. Run `yarn flow` and `yarn test`. To check that downcasting is not allowed, change the types in the new static test case in `graph.test.js` to be contravariant instead of covariant, and note that `yarn flow` fails. wchargin-branch: graph-copy --- src/core/graph.js | 4 ++++ src/core/graph.test.js | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/core/graph.js b/src/core/graph.js index 730f36c..d8c509b 100644 --- a/src/core/graph.js +++ b/src/core/graph.js @@ -41,6 +41,10 @@ export class Graph { this._inEdges = new AddressMap(); } + copy(): Graph<$Supertype, $Supertype> { + return Graph.mergeConservative(new Graph(), this); + } + equals(that: Graph): boolean { return this._nodes.equals(that._nodes) && this._edges.equals(that._edges); } diff --git a/src/core/graph.test.js b/src/core/graph.test.js index 2ddd9bc..cf5b740 100644 --- a/src/core/graph.test.js +++ b/src/core/graph.test.js @@ -631,5 +631,32 @@ describe("graph", () => { }); }); }); + + describe("copy", () => { + it("separates references from the original", () => { + const g1 = demoData.advancedMealGraph(); + const g2 = g1.copy(); + const newNode = () => ({ + address: demoData.makeAddress("brand-new"), + payload: 777, + }); + g2.addNode(newNode()); + expect(g1.getNode(newNode().address)).toBeUndefined(); + expect(g2.getNode(newNode().address)).toEqual(newNode()); + }); + + it("yields a result equal to the original", () => { + const g1 = demoData.advancedMealGraph(); + const g2 = g1.copy(); + expect(g1.equals(g2)).toBe(true); + expect(g1.equals(demoData.advancedMealGraph())).toBe(true); + }); + + function itAllowsUpcastingPayloadTypes( + g: Graph<{x: string, y: number}, boolean> + ): Graph<{x: string}, ?boolean> { + return g.copy(); + } + }); }); });