split apart combine and partition diffs steps

This commit is contained in:
Mitchell Rosen 2024-04-17 20:46:39 -04:00
parent 6a847aedae
commit 4e42d38385
11 changed files with 501 additions and 351 deletions

View File

@ -74,7 +74,7 @@ unfoldNametree f x =
-- > }
-- > }
--
-- into an equivalent-but-flatter association between names and definitions, like
-- into an equivalent-but-flat association between names and definitions, like
--
-- > {
-- > "foo" = #bar,

View File

@ -14,6 +14,7 @@ import Data.List qualified as List
import Data.List.NonEmpty (pattern (:|))
import Data.Map.Merge.Strict qualified as Map
import Data.Map.Strict qualified as Map
import Data.Semialign (unzip)
import Data.Set qualified as Set
import Data.Text qualified as Text
import Data.Text.IO qualified as Text
@ -52,13 +53,14 @@ import Unison.Codebase.SqliteCodebase.Branch.Cache (newBranchCache)
import Unison.Codebase.SqliteCodebase.Conversions qualified as Conversions
import Unison.ConstructorReference (ConstructorReference, GConstructorReference (..))
import Unison.Debug qualified as Debug
import Unison.Merge.CombineDiffs (combineDiffs)
import Unison.Merge.CombineDiffs (CombinedDiffOp, combineDiffs)
import Unison.Merge.Database (MergeDatabase (..), makeMergeDatabase, referent2to1)
import Unison.Merge.DeclCoherencyCheck (IncoherentDeclReason (..), checkDeclCoherency)
import Unison.Merge.DeclNameLookup (DeclNameLookup (..), expectConstructorNames)
import Unison.Merge.Diff qualified as Merge
import Unison.Merge.DiffOp (DiffOp (..))
import Unison.Merge.Libdeps qualified as Merge
import Unison.Merge.PartitionCombinedDiffs (partitionCombinedDiffs)
import Unison.Merge.PreconditionViolation qualified as Merge
import Unison.Merge.Synhashed (Synhashed (..))
import Unison.Merge.Synhashed qualified as Synhashed
@ -92,7 +94,7 @@ import Unison.UnisonFile (UnisonFile')
import Unison.UnisonFile qualified as UnisonFile
import Unison.Util.BiMultimap (BiMultimap)
import Unison.Util.BiMultimap qualified as BiMultimap
import Unison.Util.Defns (Defns (..), DefnsF, DefnsF2, DefnsF3, alignDefnsWith, zipDefnsWith)
import Unison.Util.Defns (Defns (..), DefnsF, DefnsF2, DefnsF3, alignDefnsWith, defnsAreEmpty, zipDefnsWith)
import Unison.Util.Map qualified as Map
import Unison.Util.Nametree (Nametree (..), flattenNametree, traverseNametreeWithName, unflattenNametree)
import Unison.Util.Pretty (ColorText, Pretty)
@ -117,20 +119,20 @@ handleMerge bobBranchName = do
-- Create a bunch of cached database lookup functions
db <- makeMergeDatabase codebase
-- Load the current project branch ("alice"), and the branch from the same project to merge in ("bob")
mergeInfo <- loadMergeInfo bobBranchName
-- Load the current project branch ("Alice"), and the branch from the same project to merge in ("Bob")
info <- loadMergeInfo bobBranchName
-- Load alice, bob, and LCA branches
-- Load Alice/Bob/LCA branches
branches <-
Cli.runTransactionWithRollback \abort -> do
loadV2Branches =<< loadV2Causals abort db mergeInfo
loadV2Branches =<< loadV2Causals abort db info
-- Load alice, bob, and LCA definitions + decl names
(declNameLookups, defns) <-
-- Load Alice/Bob/LCA definitions and decl name lookups
(defns, declNameLookups) <-
Cli.runTransactionWithRollback \abort -> do
loadDefns abort db mergeInfo branches
loadDefns abort db info.projectBranches branches
liftIO (debugFunctions.debugDefns declNameLookups defns)
liftIO (debugFunctions.debugDefns defns declNameLookups)
-- Diff LCA->Alice and LCA->Bob
diffs <-
@ -140,15 +142,20 @@ handleMerge bobBranchName = do
liftIO (debugFunctions.debugDiffs diffs)
-- Bail early if it looks like we can't proceed with the merge, because Alice or Bob has one or more conflicted alias
whenJust (findOneConflictedAlias mergeInfo.projectBranches defns.lca diffs) \violation ->
whenJust (findOneConflictedAlias info.projectBranches defns.lca diffs) \violation ->
Cli.returnEarly (mergePreconditionViolationToOutput violation)
-- Combine the LCA->Alice and LCA->Bob diffs together into the conflicted things and the unconflicted things
-- Combine the LCA->Alice and LCA->Bob diffs together
let diff = combineDiffs diffs
liftIO (debugFunctions.debugCombinedDiffs diff)
-- Partition the combined diff into the conflicted things and the unconflicted things
(conflicts, unconflicts) <-
combineDiffs (ThreeWay.forgetLca declNameLookups) (ThreeWay.forgetLca defns) diffs & onLeft \name ->
partitionCombinedDiffs (ThreeWay.forgetLca defns) (ThreeWay.forgetLca declNameLookups) diff & onLeft \name ->
Cli.returnEarly (mergePreconditionViolationToOutput (Merge.ConflictInvolvingBuiltin name))
liftIO (debugFunctions.debugCombinedDiffs conflicts unconflicts)
liftIO (debugFunctions.debugPartitionedDiff conflicts unconflicts)
-- Identify the dependents we need to pull into the Unison file (either first for typechecking, if there aren't
-- conflicts, or else for manual conflict resolution without a typechecking step, if there are)
@ -176,12 +183,7 @@ handleMerge bobBranchName = do
let thisMergeHasConflicts =
-- Eh, they'd either both be null, or neither, but just check both maps anyway
or
[ not (Map.null conflicts.alice.terms),
not (Map.null conflicts.alice.types),
not (Map.null conflicts.bob.terms),
not (Map.null conflicts.bob.types)
]
not (defnsAreEmpty conflicts.alice) || not (defnsAreEmpty conflicts.bob)
-- Create the Unison file, which may have conflicts, in which case we don't bother trying to parse and typecheck it.
unisonFile <-
@ -218,7 +220,7 @@ handleMerge bobBranchName = do
else prettyParseTypecheck unisonFile pped parsingEnv <&> eitherToMaybe
case maybeTypecheckedUnisonFile of
Nothing -> promptUser mergeInfo (Pretty.prettyUnisonFile pped unisonFile) newBranchIO
Nothing -> promptUser info (Pretty.prettyUnisonFile pped unisonFile) newBranchIO
Just tuf -> do
mergedBranchPlusTuf <-
Cli.runTransactionWithRollback \abort -> do
@ -226,11 +228,11 @@ handleMerge bobBranchName = do
updates <- typecheckedUnisonFileToBranchUpdates abort undefined tuf
pure (Branch.batchUpdates updates newBranchIO)
Cli.stepAt
(textualDescriptionOfMerge mergeInfo)
( Path.unabsolute mergeInfo.paths.alice,
(textualDescriptionOfMerge info)
( Path.unabsolute info.paths.alice,
const mergedBranchPlusTuf
)
Cli.respond (Output.MergeSuccess (aliceProjectAndBranchName mergeInfo) (bobProjectAndBranchName mergeInfo))
Cli.respond (Output.MergeSuccess (aliceProjectAndBranchName info) (bobProjectAndBranchName info))
aliceProjectAndBranchName :: MergeInfo -> ProjectAndBranch ProjectName ProjectBranchName
aliceProjectAndBranchName mergeInfo =
@ -293,29 +295,25 @@ loadV2Branches causals = do
loadDefns ::
(forall a. Output -> Transaction a) ->
MergeDatabase ->
MergeInfo ->
TwoWay ProjectBranch ->
TwoOrThreeWay (V2.Branch Transaction) ->
Transaction
( ThreeWay DeclNameLookup,
ThreeWay (Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name))
( ThreeWay (Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name)),
ThreeWay DeclNameLookup
)
loadDefns abort0 db info branches = do
loadDefns abort0 db projectBranches branches = do
lcaDefns0 <-
case branches.lca of
Nothing -> pure Nametree {value = Defns Map.empty Map.empty, children = Map.empty}
Just lcaBranch -> loadNamespaceInfo abort db lcaBranch
(lcaDeclNameLookup, lcaDefns1) <-
assertNamespaceSatisfiesPreconditions db abort Nothing (fromMaybe V2.Branch.empty branches.lca) lcaDefns0
aliceDefns0 <- loadNamespaceInfo abort db branches.alice
(aliceDeclNameLookup, aliceDefns1) <-
assertNamespaceSatisfiesPreconditions db abort (Just info.projectBranches.alice.name) branches.alice aliceDefns0
bobDefns0 <- loadNamespaceInfo abort db branches.bob
(bobDeclNameLookup, bobDefns1) <-
assertNamespaceSatisfiesPreconditions db abort (Just info.projectBranches.bob.name) branches.bob bobDefns0
pure
( ThreeWay {lca = lcaDeclNameLookup, alice = aliceDeclNameLookup, bob = bobDeclNameLookup},
ThreeWay {lca = lcaDefns1, alice = aliceDefns1, bob = bobDefns1}
)
lca <- assertNamespaceSatisfiesPreconditions db abort Nothing (fromMaybe V2.Branch.empty branches.lca) lcaDefns0
alice <- assertNamespaceSatisfiesPreconditions db abort (Just projectBranches.alice.name) branches.alice aliceDefns0
bob <- assertNamespaceSatisfiesPreconditions db abort (Just projectBranches.bob.name) branches.bob bobDefns0
pure (unzip ThreeWay {lca, alice, bob})
where
abort :: Merge.PreconditionViolation -> Transaction void
abort =
@ -784,7 +782,7 @@ defnsRangeToNames Defns {terms, types} =
types = Relation.fromMap types
}
palonka :: DefnsF (Map Name) Referent TypeReference -> Either () DeclNameLookup
palonka :: HasCallStack => DefnsF (Map Name) Referent TypeReference -> Either () DeclNameLookup
palonka defns = do
conToName <- bazinga defns.terms
@ -962,21 +960,23 @@ assertNamespaceSatisfiesPreconditions ::
Maybe ProjectBranchName ->
V2.Branch Transaction ->
Nametree (DefnsF (Map NameSegment) Referent TypeReference) ->
Transaction (DeclNameLookup, Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name))
Transaction (Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name), DeclNameLookup)
assertNamespaceSatisfiesPreconditions db abort maybeBranchName branch defns = do
Map.lookup NameSegment.libSegment branch.children `whenJust` \libdepsCausal -> do
whenJust (Map.lookup NameSegment.libSegment branch.children) \libdepsCausal -> do
libdepsBranch <- libdepsCausal.value
when (not (Map.null libdepsBranch.terms) || not (Map.null libdepsBranch.types)) do
abort Merge.DefnsInLib
declNameLookup <-
checkDeclCoherency db.loadDeclNumConstructors defns
& onLeftM (abort . incoherentDeclReasonToMergePreconditionViolation)
pure
( declNameLookup,
Defns
( Defns
{ terms = flattenNametree (view #terms) defns,
types = flattenNametree (view #types) defns
}
},
declNameLookup
)
where
incoherentDeclReasonToMergePreconditionViolation :: IncoherentDeclReason -> Merge.PreconditionViolation
@ -1105,11 +1105,12 @@ libdepsToBranch0 db libdeps = do
data DebugFunctions = DebugFunctions
{ debugDefns ::
ThreeWay DeclNameLookup ->
ThreeWay (Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name)) ->
ThreeWay DeclNameLookup ->
IO (),
debugDiffs :: TwoWay (DefnsF3 (Map Name) DiffOp Synhashed Referent TypeReference) -> IO (),
debugCombinedDiffs ::
debugCombinedDiffs :: DefnsF2 (Map Name) CombinedDiffOp Referent TypeReference -> IO (),
debugPartitionedDiff ::
TwoWay (DefnsF (Map Name) TermReferenceId TypeReferenceId) ->
DefnsF Unconflicts Referent TypeReference ->
IO (),
@ -1127,6 +1128,7 @@ realDebugFunctions =
{ debugDefns = realDebugDefns,
debugDiffs = realDebugDiffs,
debugCombinedDiffs = realDebugCombinedDiffs,
debugPartitionedDiff = realDebugPartitionedDiff,
debugDependents = realDebugDependents,
debugMergedDefns = realDebugMergedDefns,
debugMergedConstructorNames = realDebugMergedConstructorNames
@ -1134,13 +1136,13 @@ realDebugFunctions =
fakeDebugFunctions :: DebugFunctions
fakeDebugFunctions =
DebugFunctions mempty mempty mempty mempty mempty mempty
DebugFunctions mempty mempty mempty mempty mempty mempty mempty
realDebugDefns ::
ThreeWay DeclNameLookup ->
ThreeWay (Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name)) ->
ThreeWay DeclNameLookup ->
IO ()
realDebugDefns declNameLookups defns = do
realDebugDefns defns declNameLookups = do
Text.putStrLn "\n=== Alice's definitions ==="
for_ (Map.toList (BiMultimap.range defns.alice.terms)) \(name, ref) ->
Text.putStrLn ("term " <> Name.toText name <> " => " <> tShow ref)
@ -1176,11 +1178,18 @@ realDebugDiffs diffs = do
for_ (Map.toList diffs.bob.types) \(name, op) ->
Text.putStrLn ("type " <> Name.toText name <> " => " <> tShow (Synhashed.hash <$> op))
realDebugCombinedDiffs ::
realDebugCombinedDiffs :: DefnsF2 (Map Name) CombinedDiffOp Referent TypeReference -> IO ()
realDebugCombinedDiffs diff = do
for_ (Map.toList diff.terms) \(name, op) ->
Text.putStrLn ("term " <> Name.toText name <> " => " <> tShow op)
for_ (Map.toList diff.types) \(name, op) ->
Text.putStrLn ("type " <> Name.toText name <> " => " <> tShow op)
realDebugPartitionedDiff ::
TwoWay (DefnsF (Map Name) TermReferenceId TypeReferenceId) ->
DefnsF Unconflicts Referent TypeReference ->
IO ()
realDebugCombinedDiffs conflicts unconflicts = do
realDebugPartitionedDiff conflicts unconflicts = do
when (not (Map.null conflicts.alice.terms) || not (Map.null conflicts.alice.types)) do
Text.putStrLn "\n=== Alice's conflicts === "
for_ (Map.toList conflicts.alice.terms) \(name, ref) ->

View File

@ -8,3 +8,4 @@ data AliceIorBob
= OnlyAlice
| OnlyBob
| AliceAndBob
deriving stock (Show)

View File

@ -2,306 +2,63 @@
-- | Combine two diffs together.
module Unison.Merge.CombineDiffs
( combineDiffs,
( CombinedDiffOp (..),
combineDiffs,
)
where
import Control.Lens (Lens', over, view, (%~), (.~))
import Data.Bitraversable (bitraverse)
import Data.Map.Strict qualified as Map
import Data.Semialign (alignWith)
import Data.These (These (..))
import Unison.Merge.AliceIorBob (AliceIorBob (..))
import Unison.Merge.AliceXorBob (AliceXorBob (..))
import Unison.Merge.AliceXorBob qualified as AliceXorBob
import Unison.Merge.DeclNameLookup (DeclNameLookup (..), expectConstructorNames, expectDeclName)
import Unison.Merge.DiffOp (DiffOp (..))
import Unison.Merge.Synhashed (Synhashed (..))
import Unison.Merge.TwoDiffOps (TwoDiffOps (..), combineTwoDiffOps)
import Unison.Merge.TwoWay (TwoWay (..), twoWay)
import Unison.Merge.TwoWay qualified as TwoWay
import Unison.Merge.TwoWayI (TwoWayI (..))
import Unison.Merge.TwoWayI qualified as TwoWayI
import Unison.Merge.Unconflicts (Unconflicts (..))
import Unison.Merge.Unconflicts qualified as Unconflicts
import Unison.Name (Name)
import Unison.Prelude hiding (catMaybes)
import Unison.Reference (Reference' (..), TermReference, TermReferenceId, TypeReference, TypeReferenceId)
import Unison.Reference qualified as Reference
import Unison.Reference (TypeReference)
import Unison.Referent (Referent)
import Unison.Referent qualified as Referent
import Unison.Util.BiMultimap (BiMultimap)
import Unison.Util.BiMultimap qualified as BiMultimap
import Unison.Util.Defns (Defns (..), DefnsF, DefnsF2, DefnsF3, DefnsF4, defnsAreEmpty)
import Unison.Util.Map qualified as Map
import Unison.Util.Defns (DefnsF2, DefnsF3)
-- | The combined result of two diffs on the same thing.
data CombinedDiffOps ref
= CombinedDiffOps'Add !AliceIorBob !ref
| CombinedDiffOps'Delete !AliceIorBob !ref -- old value
| CombinedDiffOps'Update !AliceIorBob !ref !ref -- old value, new value
data CombinedDiffOp a
= CombinedDiffOp'Add !AliceIorBob !a
| CombinedDiffOp'Delete !AliceIorBob !a -- old value
| CombinedDiffOp'Update !AliceIorBob !a !a -- old value, new value
| -- An add-add or an update-update conflict. We don't consider update-delete a conflict; the delete gets ignored.
CombinedDiffOps'Conflict !(TwoWay ref)
CombinedDiffOp'Conflict !(TwoWay a)
deriving stock (Show)
-- | Combine LCA->Alice diff and LCA->Bob diff, then partition into conflicted and unconflicted things.
-- | Combine LCA->Alice diff and LCA->Bob diff.
combineDiffs ::
TwoWay DeclNameLookup ->
TwoWay (Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name)) ->
TwoWay (DefnsF3 (Map Name) DiffOp Synhashed Referent TypeReference) ->
Either
Name
( TwoWay (DefnsF (Map Name) TermReferenceId TypeReferenceId),
DefnsF Unconflicts Referent TypeReference
)
combineDiffs declNameLookups defns diffs = do
let diffs1 :: DefnsF4 TwoWay (Map Name) DiffOp Synhashed Referent TypeReference
diffs1 =
TwoWay.sequenceDefns diffs
let diffs2 :: DefnsF2 (Map Name) CombinedDiffOps Referent TypeReference
diffs2 =
let f = twoWay (alignWith combine)
in bimap f f diffs1
let conflicts0 = identifyConflicts declNameLookups defns diffs2
let unconflicts = identifyUnconflicts declNameLookups conflicts0 diffs2
conflicts <- assertThereAreNoBuiltins conflicts0
Right (conflicts, unconflicts)
identifyConflicts ::
HasCallStack =>
TwoWay DeclNameLookup ->
TwoWay (Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name)) ->
DefnsF2 (Map Name) CombinedDiffOps Referent TypeReference ->
TwoWay (DefnsF (Map Name) TermReference TypeReference)
identifyConflicts declNameLookups defns =
loop . makeInitialIdentifyConflictsState
DefnsF2 (Map Name) CombinedDiffOp Referent TypeReference
combineDiffs =
bimap f f . TwoWay.sequenceDefns
where
loop :: S -> TwoWay (DefnsF (Map Name) TermReference TypeReference)
loop s =
case (view myTermStack_ s, view myTypeStack_ s, defnsAreEmpty (view theirStacks_ s)) of
(name : names, _, _) -> loop (poppedTerm name (s & myTermStack_ .~ names))
([], name : names, _) -> loop (poppedType name (s & myTypeStack_ .~ names))
([], [], False) -> loop (s & #me %~ AliceXorBob.swap)
([], [], True) -> s.conflicts
where
poppedTerm :: Name -> S -> S
poppedTerm name =
case BiMultimap.lookupRan name (view (me_ . #terms) defns) of
Nothing -> id
Just (Referent.Ref ref) -> over myTermConflicts_ (Map.insert name ref)
Just (Referent.Con _ _) -> over myTypeStack_ (expectDeclName myDeclNameLookup name :)
f = twoWay (alignWith combine)
poppedType :: Name -> S -> S
poppedType name s =
fromMaybe s do
ref <- BiMultimap.lookupRan name (view (me_ . #types) defns)
conflicts <- Map.upsertF (maybe (Just ref) (const Nothing)) name (view myTypeConflicts_ s)
Just $
s
& myTypeConflicts_ .~ conflicts
& case ref of
ReferenceBuiltin _ -> id -- builtin types don't have constructors
ReferenceDerived _ -> theirTermStack_ %~ (expectConstructorNames myDeclNameLookup name ++)
me_ :: Lens' (TwoWay a) a
me_ = TwoWay.who_ s.me
myConflicts_ :: Lens' S (DefnsF (Map Name) TermReference TypeReference)
myConflicts_ = #conflicts . me_
myTermConflicts_ :: Lens' S (Map Name TermReference)
myTermConflicts_ = myConflicts_ . #terms
myTypeConflicts_ :: Lens' S (Map Name TermReference)
myTypeConflicts_ = myConflicts_ . #types
myStacks_ :: Lens' S (DefnsF [] Name Name)
myStacks_ = #stacks . me_
myTermStack_ :: Lens' S [Name]
myTermStack_ = myStacks_ . #terms
myTypeStack_ :: Lens' S [Name]
myTypeStack_ = myStacks_ . #types
myDeclNameLookup :: DeclNameLookup
myDeclNameLookup = view me_ declNameLookups
them_ :: Lens' (TwoWay a) a
them_ = TwoWay.who_ (AliceXorBob.swap s.me)
theirStacks_ :: Lens' S (DefnsF [] Name Name)
theirStacks_ = #stacks . them_
theirTermStack_ :: Lens' S [Name]
theirTermStack_ = theirStacks_ . #terms
identifyUnconflicts ::
TwoWay DeclNameLookup ->
TwoWay (DefnsF (Map Name) TermReference TypeReference) ->
DefnsF2 (Map Name) CombinedDiffOps Referent TypeReference ->
DefnsF Unconflicts Referent TypeReference
identifyUnconflicts declNameLookups conflicts =
bimap (identifyTermUnconflicts declNameLookups conflicts) (identifyTypeUnconflicts (view #types <$> conflicts))
identifyTermUnconflicts ::
TwoWay DeclNameLookup ->
TwoWay (DefnsF (Map Name) TermReference TypeReference) ->
Map Name (CombinedDiffOps Referent) ->
Unconflicts Referent
identifyTermUnconflicts declNameLookups conflicts =
Map.foldlWithKey' (\acc name op -> f name op acc) Unconflicts.empty
where
f :: Name -> CombinedDiffOps Referent -> Unconflicts Referent -> Unconflicts Referent
f name = \case
CombinedDiffOps'Add who ref ->
case ref of
Referent.Ref _ -> keepIt1
Referent.Con _ _ -> if constructor who then ignoreIt else keepIt1
where
keepIt1 = keepIt #adds who name ref
CombinedDiffOps'Update who _old new ->
case new of
Referent.Ref _ ->
case who of
OnlyAlice -> if termIsConflicted.alice then ignoreIt else keepIt1
OnlyBob -> if termIsConflicted.bob then ignoreIt else keepIt1
AliceAndBob -> keepIt1
Referent.Con _ _ -> if constructor who then ignoreIt else keepIt1
where
keepIt1 = keepIt #updates who name new
CombinedDiffOps'Delete who ref -> keepIt #deletes who name ref
CombinedDiffOps'Conflict _ -> ignoreIt
where
-- Ignore added/updated constructors whose types are conflicted
constructor :: AliceIorBob -> Bool
constructor = \case
OnlyAlice -> constructorHasConflictedType.alice
OnlyBob -> constructorHasConflictedType.bob
AliceAndBob -> TwoWay.or constructorHasConflictedType
constructorHasConflictedType :: TwoWay Bool
constructorHasConflictedType =
(\conflicts1 declNameLookup -> Map.member (expectDeclName declNameLookup name) conflicts1.types)
<$> conflicts
<*> declNameLookups
termIsConflicted :: TwoWay Bool
termIsConflicted =
Map.member name . view #terms <$> conflicts
identifyTypeUnconflicts ::
TwoWay (Map Name TypeReference) ->
Map Name (CombinedDiffOps TypeReference) ->
Unconflicts TypeReference
identifyTypeUnconflicts conflicts =
Map.foldlWithKey' (\acc name ref -> f name ref acc) Unconflicts.empty
where
f :: Name -> CombinedDiffOps TypeReference -> Unconflicts TypeReference -> Unconflicts TypeReference
f name = \case
CombinedDiffOps'Add who ref -> addOrUpdate #adds who ref
CombinedDiffOps'Update who _old new -> addOrUpdate #updates who new
CombinedDiffOps'Delete who ref -> keepIt #deletes who name ref
CombinedDiffOps'Conflict _ -> ignoreIt
where
addOrUpdate :: Lens' (Unconflicts v) (TwoWayI (Map Name v)) -> AliceIorBob -> v -> Unconflicts v -> Unconflicts v
addOrUpdate l who ref =
case who of
OnlyAlice -> if typeIsConflicted.alice then ignoreIt else keepIt1
OnlyBob -> if typeIsConflicted.bob then ignoreIt else keepIt1
AliceAndBob -> if TwoWay.or typeIsConflicted then ignoreIt else keepIt1
where
keepIt1 = keepIt l who name ref
typeIsConflicted :: TwoWay Bool
typeIsConflicted =
Map.member name <$> conflicts
keepIt ::
Lens' (Unconflicts v) (TwoWayI (Map Name v)) ->
AliceIorBob ->
Name ->
v ->
Unconflicts v ->
Unconflicts v
keepIt what who name ref =
over (what . TwoWayI.who_ who) (Map.insert name ref)
ignoreIt :: Unconflicts v -> Unconflicts v
ignoreIt =
id
makeInitialIdentifyConflictsState :: DefnsF2 (Map Name) CombinedDiffOps Referent TypeReference -> S
makeInitialIdentifyConflictsState diff =
S
{ me = Alice,
conflicts = mempty,
stacks =
let f = TwoWay.bothWays . justTheConflictedNames
in bitraverse f f diff
}
-- Given a combined diff, return the names that are conflicted.
justTheConflictedNames :: Map Name (CombinedDiffOps a) -> [Name]
justTheConflictedNames =
Map.foldlWithKey' f []
where
f :: [Name] -> Name -> CombinedDiffOps term -> [Name]
f names name = \case
CombinedDiffOps'Conflict _ -> name : names
CombinedDiffOps'Add _ _ -> names
CombinedDiffOps'Delete _ _ -> names
CombinedDiffOps'Update _ _ _ -> names
data S = S
{ me :: !AliceXorBob,
conflicts :: !(TwoWay (DefnsF (Map Name) TermReference TypeReference)),
stacks :: !(TwoWay (DefnsF [] Name Name))
}
deriving stock (Generic)
combine :: These (DiffOp (Synhashed ref)) (DiffOp (Synhashed ref)) -> CombinedDiffOps ref
combine :: These (DiffOp (Synhashed ref)) (DiffOp (Synhashed ref)) -> CombinedDiffOp ref
combine =
combineTwoDiffOps >>> \case
TwoDiffOps'Add who x -> CombinedDiffOps'Add (xor2ior who) x.value
TwoDiffOps'Delete who x -> CombinedDiffOps'Delete (xor2ior who) x.value
TwoDiffOps'Update who old new -> CombinedDiffOps'Update (xor2ior who) old.value new.value
TwoDiffOps'Add who x -> CombinedDiffOp'Add (xor2ior who) x.value
TwoDiffOps'Delete who x -> CombinedDiffOp'Delete (xor2ior who) x.value
TwoDiffOps'Update who old new -> CombinedDiffOp'Update (xor2ior who) old.value new.value
TwoDiffOps'AddAdd TwoWay {alice, bob}
| alice /= bob -> CombinedDiffOps'Conflict TwoWay {alice = alice.value, bob = bob.value}
| otherwise -> CombinedDiffOps'Add AliceAndBob alice.value
TwoDiffOps'DeleteDelete x -> CombinedDiffOps'Delete AliceAndBob x.value
| alice /= bob -> CombinedDiffOp'Conflict TwoWay {alice = alice.value, bob = bob.value}
| otherwise -> CombinedDiffOp'Add AliceAndBob alice.value
TwoDiffOps'DeleteDelete x -> CombinedDiffOp'Delete AliceAndBob x.value
-- These two are not a conflicts, perhaps only temporarily, because it's easier to implement. We just ignore these
-- deletes and keep the updates.
TwoDiffOps'DeleteUpdate old new -> CombinedDiffOps'Update OnlyBob old.value new.value
TwoDiffOps'UpdateDelete old new -> CombinedDiffOps'Update OnlyAlice old.value new.value
TwoDiffOps'DeleteUpdate old new -> CombinedDiffOp'Update OnlyBob old.value new.value
TwoDiffOps'UpdateDelete old new -> CombinedDiffOp'Update OnlyAlice old.value new.value
TwoDiffOps'UpdateUpdate old TwoWay {alice, bob}
| alice /= bob -> CombinedDiffOps'Conflict TwoWay {alice = alice.value, bob = bob.value}
| otherwise -> CombinedDiffOps'Update AliceAndBob old.value alice.value
| alice /= bob -> CombinedDiffOp'Conflict TwoWay {alice = alice.value, bob = bob.value}
| otherwise -> CombinedDiffOp'Update AliceAndBob old.value alice.value
xor2ior :: AliceXorBob -> AliceIorBob
xor2ior = \case
Alice -> OnlyAlice
Bob -> OnlyBob
assertThereAreNoBuiltins ::
TwoWay (DefnsF (Map Name) TermReference TypeReference) ->
Either Name (TwoWay (DefnsF (Map Name) TermReferenceId TypeReferenceId))
assertThereAreNoBuiltins =
traverse (bitraverse (Map.traverseWithKey assertTermIsntBuiltin) (Map.traverseWithKey assertTypeIsntBuiltin))
where
assertTermIsntBuiltin :: Name -> TermReference -> Either Name TermReferenceId
assertTermIsntBuiltin name ref =
case Reference.toId ref of
Nothing -> Left name
Just refId -> Right refId
-- Same body as above, but could be different some day (e.g. return value tells you what namespace)
assertTypeIsntBuiltin :: Name -> TypeReference -> Either Name TypeReferenceId
assertTypeIsntBuiltin name ref =
case Reference.toId ref of
Nothing -> Left name
Just refId -> Right refId

View File

@ -37,16 +37,14 @@ import Unison.Util.BiMultimap qualified as BiMultimap
import Unison.Util.Defns (Defns (..), DefnsF2, DefnsF3, zipDefnsWith)
import Unison.Var (Var)
-- | @nameBasedNamespaceDiff db defns@ returns Alice's and Bob's name-based namespace diffs, each in the form:
-- | @nameBasedNamespaceDiff db declNameLookups defns@ returns Alice's and Bob's name-based namespace diffs, each in the
-- form:
--
-- > decls :: Map Name (DiffOp (Synhashed TypeReference))
-- > terms :: Map Name (DiffOp (Synhashed Referent))
-- > types :: Map Name (DiffOp (Synhashed TypeReference))
--
-- where each name is paired with its diff-op (added, deleted, or updated), relative to the LCA between Alice and Bob's
-- branches. If the hash of a name did not change, it will not appear in the map.
--
-- If there is no LCA, this operation is equivalent to a two-way diff, where every name in each of Alice and Bob's
-- branches is considered an add.
nameBasedNamespaceDiff ::
MergeDatabase ->
ThreeWay DeclNameLookup ->
@ -117,12 +115,10 @@ diffNamespaceDefns =
deepNamespaceDefinitionsToPpe :: Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name) -> PrettyPrintEnv
deepNamespaceDefinitionsToPpe Defns {terms, types} =
PrettyPrintEnv
(\ref -> arbitraryName ref terms)
(\ref -> arbitraryName ref types)
PrettyPrintEnv (arbitraryName terms) (arbitraryName types)
where
arbitraryName :: Ord ref => ref -> BiMultimap ref Name -> [(HQ'.HashQualified Name, HQ'.HashQualified Name)]
arbitraryName ref names =
arbitraryName :: Ord ref => BiMultimap ref Name -> ref -> [(HQ'.HashQualified Name, HQ'.HashQualified Name)]
arbitraryName names ref =
BiMultimap.lookupDom ref names
& Set.lookupMin
& maybe [] \name -> [(HQ'.NameOnly name, HQ'.NameOnly name)]

View File

@ -0,0 +1,268 @@
{-# LANGUAGE OverloadedRecordDot #-}
module Unison.Merge.PartitionCombinedDiffs
( partitionCombinedDiffs,
)
where
import Control.Lens (Lens', over, view, (%~), (.~))
import Data.Bitraversable (bitraverse)
import Data.Map.Strict qualified as Map
import Unison.Merge.AliceIorBob (AliceIorBob (..))
import Unison.Merge.AliceXorBob (AliceXorBob (..))
import Unison.Merge.AliceXorBob qualified as AliceXorBob
import Unison.Merge.CombineDiffs (CombinedDiffOp (..))
import Unison.Merge.DeclNameLookup (DeclNameLookup (..), expectConstructorNames, expectDeclName)
import Unison.Merge.TwoWay (TwoWay (..))
import Unison.Merge.TwoWay qualified as TwoWay
import Unison.Merge.TwoWayI (TwoWayI (..))
import Unison.Merge.TwoWayI qualified as TwoWayI
import Unison.Merge.Unconflicts (Unconflicts (..))
import Unison.Merge.Unconflicts qualified as Unconflicts
import Unison.Name (Name)
import Unison.Prelude hiding (catMaybes)
import Unison.Reference (Reference' (..), TermReference, TermReferenceId, TypeReference, TypeReferenceId)
import Unison.Reference qualified as Reference
import Unison.Referent (Referent)
import Unison.Referent qualified as Referent
import Unison.Util.BiMultimap (BiMultimap)
import Unison.Util.BiMultimap qualified as BiMultimap
import Unison.Util.Defns (Defns (..), DefnsF, DefnsF2, defnsAreEmpty)
import Unison.Util.Map qualified as Map
-- | Combine LCA->Alice diff and LCA->Bob diff, then partition into conflicted and unconflicted things.
partitionCombinedDiffs ::
TwoWay (Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name)) ->
TwoWay DeclNameLookup ->
DefnsF2 (Map Name) CombinedDiffOp Referent TypeReference ->
Either
Name
( TwoWay (DefnsF (Map Name) TermReferenceId TypeReferenceId),
DefnsF Unconflicts Referent TypeReference
)
partitionCombinedDiffs defns declNameLookups diffs = do
let conflicts0 = identifyConflicts declNameLookups defns diffs
let unconflicts = identifyUnconflicts declNameLookups conflicts0 diffs
conflicts <- assertThereAreNoBuiltins conflicts0
Right (conflicts, unconflicts)
data S = S
{ me :: !AliceXorBob,
conflicts :: !(TwoWay (DefnsF (Map Name) TermReference TypeReference)),
stacks :: !(TwoWay (DefnsF [] Name Name))
}
deriving stock (Generic)
makeInitialIdentifyConflictsState :: DefnsF2 (Map Name) CombinedDiffOp Referent TypeReference -> S
makeInitialIdentifyConflictsState diff =
S
{ me = Alice, -- arbitrary initial person
conflicts = mempty,
stacks =
let f = TwoWay.bothWays . justTheConflictedNames
in bitraverse f f diff
}
identifyConflicts ::
HasCallStack =>
TwoWay DeclNameLookup ->
TwoWay (Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name)) ->
DefnsF2 (Map Name) CombinedDiffOp Referent TypeReference ->
TwoWay (DefnsF (Map Name) TermReference TypeReference)
identifyConflicts declNameLookups defns =
loop . makeInitialIdentifyConflictsState
where
loop :: S -> TwoWay (DefnsF (Map Name) TermReference TypeReference)
loop s =
case (view myTermStack_ s, view myTypeStack_ s, defnsAreEmpty (view theirStacks_ s)) of
(name : names, _, _) -> loop (poppedTerm name (s & myTermStack_ .~ names))
([], name : names, _) -> loop (poppedType name (s & myTypeStack_ .~ names))
([], [], False) -> loop (s & #me %~ AliceXorBob.swap)
([], [], True) -> s.conflicts
where
poppedTerm :: Name -> S -> S
poppedTerm name =
case BiMultimap.lookupRan name (view myTerms_ defns) of
Nothing -> id
Just (Referent.Ref ref) -> over myTermConflicts_ (Map.insert name ref)
Just (Referent.Con _ _) -> over myTypeStack_ (expectDeclName myDeclNameLookup name :)
poppedType :: Name -> S -> S
poppedType name s =
fromMaybe s do
ref <- BiMultimap.lookupRan name (view myTypes_ defns)
-- Bail early here (by returning Nothing in the first argument to upsertF) if we've already recorded this
-- type as conflicted, because in that case we've already added its constructor names to the other person's
-- term stack, and we only want to do that once.
typeConflicts <- Map.upsertF (maybe (Just ref) (const Nothing)) name (view myTypeConflicts_ s)
Just $
s
& myTypeConflicts_ .~ typeConflicts
& case ref of
ReferenceBuiltin _ -> id -- builtin types don't have constructors
ReferenceDerived _ -> theirTermStack_ %~ (expectConstructorNames myDeclNameLookup name ++)
me_ :: Lens' (TwoWay a) a
me_ = TwoWay.who_ s.me
myTerms_ :: Lens' (TwoWay (Defns terms types)) terms
myTerms_ = me_ . #terms
myTypes_ :: Lens' (TwoWay (Defns terms types)) types
myTypes_ = me_ . #types
myConflicts_ :: Lens' S (DefnsF (Map Name) TermReference TypeReference)
myConflicts_ = #conflicts . me_
myTermConflicts_ :: Lens' S (Map Name TermReference)
myTermConflicts_ = myConflicts_ . #terms
myTypeConflicts_ :: Lens' S (Map Name TermReference)
myTypeConflicts_ = myConflicts_ . #types
myStacks_ :: Lens' S (DefnsF [] Name Name)
myStacks_ = #stacks . me_
myTermStack_ :: Lens' S [Name]
myTermStack_ = myStacks_ . #terms
myTypeStack_ :: Lens' S [Name]
myTypeStack_ = myStacks_ . #types
myDeclNameLookup :: DeclNameLookup
myDeclNameLookup = view me_ declNameLookups
them_ :: Lens' (TwoWay a) a
them_ = TwoWay.who_ (AliceXorBob.swap s.me)
theirStacks_ :: Lens' S (DefnsF [] Name Name)
theirStacks_ = #stacks . them_
theirTermStack_ :: Lens' S [Name]
theirTermStack_ = theirStacks_ . #terms
identifyUnconflicts ::
TwoWay DeclNameLookup ->
TwoWay (DefnsF (Map Name) TermReference TypeReference) ->
DefnsF2 (Map Name) CombinedDiffOp Referent TypeReference ->
DefnsF Unconflicts Referent TypeReference
identifyUnconflicts declNameLookups conflicts =
bimap (identifyTermUnconflicts declNameLookups conflicts) (identifyTypeUnconflicts (view #types <$> conflicts))
identifyTermUnconflicts ::
TwoWay DeclNameLookup ->
TwoWay (DefnsF (Map Name) TermReference TypeReference) ->
Map Name (CombinedDiffOp Referent) ->
Unconflicts Referent
identifyTermUnconflicts declNameLookups conflicts =
Map.foldlWithKey' (\acc name op -> f name op acc) Unconflicts.empty
where
f :: Name -> CombinedDiffOp Referent -> Unconflicts Referent -> Unconflicts Referent
f name = \case
CombinedDiffOp'Add who ref ->
case ref of
Referent.Ref _ -> keepIt1
Referent.Con _ _ -> if constructor who then ignoreIt else keepIt1
where
keepIt1 = keepIt #adds who name ref
CombinedDiffOp'Update who _old new ->
case new of
Referent.Ref _ ->
case who of
OnlyAlice -> if termIsConflicted.alice then ignoreIt else keepIt1
OnlyBob -> if termIsConflicted.bob then ignoreIt else keepIt1
AliceAndBob -> keepIt1
Referent.Con _ _ -> if constructor who then ignoreIt else keepIt1
where
keepIt1 = keepIt #updates who name new
CombinedDiffOp'Delete who ref -> keepIt #deletes who name ref
CombinedDiffOp'Conflict _ -> ignoreIt
where
-- Ignore added/updated constructors whose types are conflicted
constructor :: AliceIorBob -> Bool
constructor = \case
OnlyAlice -> constructorHasConflictedType.alice
OnlyBob -> constructorHasConflictedType.bob
AliceAndBob -> TwoWay.or constructorHasConflictedType
constructorHasConflictedType :: TwoWay Bool
constructorHasConflictedType =
(\conflicts1 declNameLookup -> Map.member (expectDeclName declNameLookup name) conflicts1.types)
<$> conflicts
<*> declNameLookups
termIsConflicted :: TwoWay Bool
termIsConflicted =
Map.member name . view #terms <$> conflicts
identifyTypeUnconflicts ::
TwoWay (Map Name TypeReference) ->
Map Name (CombinedDiffOp TypeReference) ->
Unconflicts TypeReference
identifyTypeUnconflicts conflicts =
Map.foldlWithKey' (\acc name ref -> f name ref acc) Unconflicts.empty
where
f :: Name -> CombinedDiffOp TypeReference -> Unconflicts TypeReference -> Unconflicts TypeReference
f name = \case
CombinedDiffOp'Add who ref -> addOrUpdate #adds who ref
CombinedDiffOp'Update who _old new -> addOrUpdate #updates who new
CombinedDiffOp'Delete who ref -> keepIt #deletes who name ref
CombinedDiffOp'Conflict _ -> ignoreIt
where
addOrUpdate :: Lens' (Unconflicts v) (TwoWayI (Map Name v)) -> AliceIorBob -> v -> Unconflicts v -> Unconflicts v
addOrUpdate l who ref =
case who of
OnlyAlice -> if typeIsConflicted.alice then ignoreIt else keepIt1
OnlyBob -> if typeIsConflicted.bob then ignoreIt else keepIt1
AliceAndBob -> if TwoWay.or typeIsConflicted then ignoreIt else keepIt1
where
keepIt1 = keepIt l who name ref
typeIsConflicted :: TwoWay Bool
typeIsConflicted =
Map.member name <$> conflicts
keepIt ::
Lens' (Unconflicts v) (TwoWayI (Map Name v)) ->
AliceIorBob ->
Name ->
v ->
Unconflicts v ->
Unconflicts v
keepIt what who name ref =
over (what . TwoWayI.who_ who) (Map.insert name ref)
ignoreIt :: Unconflicts v -> Unconflicts v
ignoreIt =
id
-- Given a combined diff, return the names that are conflicted.
justTheConflictedNames :: Map Name (CombinedDiffOp a) -> [Name]
justTheConflictedNames =
Map.foldlWithKey' f []
where
f :: [Name] -> Name -> CombinedDiffOp term -> [Name]
f names name = \case
CombinedDiffOp'Conflict _ -> name : names
CombinedDiffOp'Add _ _ -> names
CombinedDiffOp'Delete _ _ -> names
CombinedDiffOp'Update _ _ _ -> names
assertThereAreNoBuiltins ::
TwoWay (DefnsF (Map Name) TermReference TypeReference) ->
Either Name (TwoWay (DefnsF (Map Name) TermReferenceId TypeReferenceId))
assertThereAreNoBuiltins =
traverse (bitraverse (Map.traverseWithKey assertTermIsntBuiltin) (Map.traverseWithKey assertTypeIsntBuiltin))
where
assertTermIsntBuiltin :: Name -> TermReference -> Either Name TermReferenceId
assertTermIsntBuiltin name ref =
case Reference.toId ref of
Nothing -> Left name
Just refId -> Right refId
-- Same body as above, but could be different some day (e.g. return value tells you what namespace)
assertTypeIsntBuiltin :: Name -> TypeReference -> Either Name TypeReferenceId
assertTypeIsntBuiltin name ref =
case Reference.toId ref of
Nothing -> Left name
Just refId -> Right refId

View File

@ -4,6 +4,8 @@ module Unison.Merge.ThreeWay
)
where
import Data.Semialign (Semialign (alignWith), Unzip (unzipWith), Zip (zipWith))
import Data.These (These (..))
import Unison.Merge.TwoWay (TwoWay (..))
import Unison.Prelude
@ -15,8 +17,31 @@ data ThreeWay a = ThreeWay
deriving stock (Foldable, Functor, Generic, Traversable)
instance Applicative ThreeWay where
pure x = ThreeWay x x x
ThreeWay f g h <*> ThreeWay x y z = ThreeWay (f x) (g y) (h z)
pure :: a -> ThreeWay a
pure x =
ThreeWay x x x
(<*>) :: ThreeWay (a -> b) -> ThreeWay a -> ThreeWay b
ThreeWay f g h <*> ThreeWay x y z =
ThreeWay (f x) (g y) (h z)
instance Semialign ThreeWay where
alignWith :: (These a b -> c) -> ThreeWay a -> ThreeWay b -> ThreeWay c
alignWith f (ThreeWay a b c) (ThreeWay x y z) =
ThreeWay (f (These a x)) (f (These b y)) (f (These c z))
instance Unzip ThreeWay where
unzipWith :: (c -> (a, b)) -> ThreeWay c -> (ThreeWay a, ThreeWay b)
unzipWith f (ThreeWay a b c) =
let (i, x) = f a
(j, y) = f b
(k, z) = f c
in (ThreeWay i j k, ThreeWay x y z)
instance Zip ThreeWay where
zipWith :: (a -> b -> c) -> ThreeWay a -> ThreeWay b -> ThreeWay c
zipWith f (ThreeWay a b c) (ThreeWay x y z) =
ThreeWay (f a x) (f b y) (f c z)
forgetLca :: ThreeWay a -> TwoWay a
forgetLca ThreeWay {alice, bob} =

View File

@ -26,7 +26,7 @@ data TwoWay a = TwoWay
{ alice :: a,
bob :: a
}
deriving stock (Foldable, Functor, Generic, Traversable)
deriving stock (Foldable, Functor, Generic, Show, Traversable)
deriving (Monoid, Semigroup) via (GenericSemigroupMonoid (TwoWay a))
instance Applicative TwoWay where

View File

@ -27,6 +27,7 @@ library
Unison.Merge.Diff
Unison.Merge.DiffOp
Unison.Merge.Libdeps
Unison.Merge.PartitionCombinedDiffs
Unison.Merge.PreconditionViolation
Unison.Merge.Synhash
Unison.Merge.Synhashed

View File

@ -280,11 +280,10 @@ project/alice> merge2 /bob
.> project.delete project
```
## Term conflict with a constructor
## Type update+rename conflict
In this example, Alice updates a type, while Bob "updates" one of the constructors (by changing it to a term), and adds
back a name for the constructor somewhere else. Bob didn't actually update the type itself, but there is nonetheless
a conflict between Alice's type (due to one of its constructors) and Bob's term.
Renaming a constructor is modeled as an update, so if Alice updates a type and Bob renames one of its constructors but
doesn't change its hash, that's still a conflict.
```ucm:hide
.> project.create-empty project
@ -292,9 +291,7 @@ project/main> builtins.mergeio
```
```unison
unique type Foo
= MkFooOne Nat
| MkFooTwo Nat Nat
unique type Foo = Baz Nat | Qux Text
```
```ucm
@ -303,9 +300,7 @@ project/main> branch alice
```
```unison
unique type Foo
= MkFooOne Nat Text
| MkFooTwo Nat Nat
unique type Foo = Baz Nat Nat | Qux Text
```
```ucm
@ -314,12 +309,7 @@ project/main> branch bob
```
```unison
unique type Foo
= MkFooOne Nat
| MkFooTwoRenamed Nat Nat
Foo.MkFooTwo : Text
Foo.MkFooTwo = "hello"
unique type Foo = Baz Nat | BobQux Text
```
```ucm:error
@ -331,7 +321,6 @@ project/alice> merge2 /bob
.> project.delete project
```
## Precondition violations
Let's see a number of merge precondition violations. These are conditions under which we can't perform a merge, and the

View File

@ -701,6 +701,110 @@ type Foo = MkFoo Nat Nat
type Foo = MkFoo Nat Text
```
## Type update+rename conflict
Renaming a constructor is modeled as an update, so if Alice updates a type and Bob renames one of its constructors but
doesn't change its hash, that's still a conflict.
```unison
unique type Foo = Baz Nat | Qux Text
```
```ucm
Loading changes detected in scratch.u.
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
type Foo
```
```ucm
project/main> add
⍟ I've added these definitions:
type Foo
project/main> branch alice
Done. I've created the alice branch based off of main.
Tip: Use `merge /alice /main` to merge your work back into the
main branch.
```
```unison
unique type Foo = Baz Nat Nat | Qux Text
```
```ucm
Loading changes detected in scratch.u.
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These names already exist. You can `update` them to your
new definition:
type Foo
```
```ucm
project/alice> update
Okay, I'm searching the branch for code that needs to be
updated...
Done.
project/main> branch bob
Done. I've created the bob branch based off of main.
Tip: Use `merge /bob /main` to merge your work back into the
main branch.
```
```unison
unique type Foo = Baz Nat | BobQux Text
```
```ucm
Loading changes detected in scratch.u.
I found and typechecked the definitions in scratch.u. This
file has been previously added to the codebase.
```
```ucm
project/bob> update
Okay, I'm searching the branch for code that needs to be
updated...
Done.
project/alice> merge2 /bob
I couldn't automatically merge bob into alice. However, I've
added the definitions that need attention to the top of
scratch.u.
```
```unison:added-by-ucm scratch.u
type Foo = Baz Nat Nat | Qux Text
type Foo = Baz Nat | BobQux Text
```
## Term conflict with a constructor
In this example, Alice updates a type, while Bob "updates" one of the constructors (by changing it to a term), and adds