continue attempting to properly partition diff into conflicted and unconflicted

This commit is contained in:
Mitchell Rosen 2024-04-16 18:09:56 -04:00
parent 75ae8120af
commit da3e716e49
6 changed files with 226 additions and 417 deletions

View File

@ -3,6 +3,9 @@
module Unison.Util.Defns
( Defns (..),
DefnsF,
DefnsF2,
DefnsF3,
DefnsF4,
alignDefnsWith,
defnsAreEmpty,
mapDefns,
@ -44,6 +47,15 @@ instance Bitraversable Defns where
type DefnsF f terms types =
Defns (f terms) (f types)
type DefnsF2 f g terms types =
Defns (f (g terms)) (f (g types))
type DefnsF3 f g h terms types =
Defns (f (g (h terms))) (f (g (h types)))
type DefnsF4 f g h i terms types =
Defns (f (g (h (i terms)))) (f (g (h (i types))))
alignDefnsWith :: Semialign f => (These a b -> c) -> Defns (f a) (f b) -> f c
alignDefnsWith f defns =
alignWith f defns.terms defns.types

View File

@ -92,7 +92,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, alignDefnsWith, zipDefnsWith)
import Unison.Util.Defns (Defns (..), DefnsF, DefnsF2, DefnsF3, alignDefnsWith, zipDefnsWith)
import Unison.Util.Map qualified as Map
import Unison.Util.Nametree (Nametree (..), flattenNametree, traverseNametreeWithName, unflattenNametree)
import Unison.Util.Pretty (ColorText, Pretty)
@ -451,7 +451,7 @@ bumpLca declNameLookups conflicts unconflicts dependents lca =
-- Apply the updates to the LCA
& runNamespaceUpdate (defnsRangeOnly lca)
where
deleteTheConflicts :: DefnsF NamespaceUpdate (Map Name Referent) (Map Name TypeReference)
deleteTheConflicts :: DefnsF2 NamespaceUpdate (Map Name) Referent TypeReference
deleteTheConflicts =
bimap shedConflicted shedConflicted conflictedNames
where
@ -460,7 +460,7 @@ bumpLca declNameLookups conflicts unconflicts dependents lca =
fold (conflictsToConflictedNames <$> declNameLookups <*> conflicts)
-- Compute the adds to apply to the LCA
applyTheAdds :: DefnsF NamespaceUpdate (Map Name Referent) (Map Name TypeReference)
applyTheAdds :: DefnsF2 NamespaceUpdate (Map Name) Referent TypeReference
applyTheAdds =
Defns
{ terms =
@ -484,7 +484,7 @@ bumpLca declNameLookups conflicts unconflicts dependents lca =
}
-- Compute the deletes to apply to the LCA
applyTheDeletes :: DefnsF NamespaceUpdate (Map Name Referent) (Map Name TypeReference)
applyTheDeletes :: DefnsF2 NamespaceUpdate (Map Name) Referent TypeReference
applyTheDeletes =
bimap deletes1 deletes1 unconflicts
where
@ -545,7 +545,7 @@ defnsAndLibdepsToBranch0 ::
Branch0 IO
defnsAndLibdepsToBranch0 codebase defns libdeps =
let -- Unflatten the collection of terms into tree, ditto for types
nametrees :: DefnsF Nametree (Map NameSegment Referent) (Map NameSegment TypeReference)
nametrees :: DefnsF2 Nametree (Map NameSegment) Referent TypeReference
nametrees =
bimap go go defns
@ -887,7 +887,7 @@ loadNamespaceInfo0 ::
(Monad m) =>
(V2.Referent -> m Referent) ->
V2.Branch m ->
m (Nametree (DefnsF (Map NameSegment) (Set Referent) (Set TypeReference)))
m (Nametree (DefnsF2 (Map NameSegment) Set Referent TypeReference))
loadNamespaceInfo0 referent2to1 branch = do
terms <-
branch.terms
@ -904,7 +904,7 @@ loadNamespaceInfo0_ ::
(Monad m) =>
(V2.Referent -> m Referent) ->
V2.Branch m ->
m (Nametree (DefnsF (Map NameSegment) (Set Referent) (Set TypeReference)))
m (Nametree (DefnsF2 (Map NameSegment) Set Referent TypeReference))
loadNamespaceInfo0_ referent2to1 branch = do
terms <-
branch.terms
@ -919,7 +919,7 @@ loadNamespaceInfo0_ referent2to1 branch = do
-- | Assert that there are no unconflicted names in a namespace.
assertNamespaceHasNoConflictedNames ::
Nametree (DefnsF (Map NameSegment) (Set Referent) (Set TypeReference)) ->
Nametree (DefnsF2 (Map NameSegment) Set Referent TypeReference) ->
Either Merge.PreconditionViolation (Nametree (DefnsF (Map NameSegment) Referent TypeReference))
assertNamespaceHasNoConflictedNames =
traverseNametreeWithName \names defns -> do
@ -989,7 +989,7 @@ assertNamespaceSatisfiesPreconditions db abort maybeBranchName branch defns = do
findOneConflictedAlias ::
TwoWay ProjectBranch ->
Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name) ->
TwoWay (DefnsF (Map Name) (DiffOp (Synhashed Referent)) (DiffOp (Synhashed TypeReference))) ->
TwoWay (DefnsF3 (Map Name) DiffOp Synhashed Referent TypeReference) ->
Maybe Merge.PreconditionViolation
findOneConflictedAlias projectBranchNames lcaDefns diffs =
aliceConflictedAliases <|> bobConflictedAliases
@ -1021,7 +1021,7 @@ findOneConflictedAlias projectBranchNames lcaDefns diffs =
-- This function currently doesn't return whether the conflicted alias is a decl or a term, but it certainly could.
findConflictedAlias ::
Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name) ->
DefnsF (Map Name) (DiffOp (Synhashed Referent)) (DiffOp (Synhashed TypeReference)) ->
DefnsF3 (Map Name) DiffOp Synhashed Referent TypeReference ->
Maybe (Name, Name)
findConflictedAlias defns diff =
asum [go defns.terms diff.terms, go defns.types diff.types]
@ -1132,7 +1132,7 @@ debugDefns declNameLookups defns =
debugDiffs ::
MonadIO m =>
TwoWay (DefnsF (Map Name) (DiffOp (Synhashed Referent)) (DiffOp (Synhashed TypeReference))) ->
TwoWay (DefnsF3 (Map Name) DiffOp Synhashed Referent TypeReference) ->
m ()
debugDiffs diffs =
Debug.whenDebug Debug.Merge do

View File

@ -6,18 +6,16 @@ module Unison.Merge.CombineDiffs
)
where
import Control.Lens (Lens', over, view, (%~), (.~), _1, _2)
import Control.Lens (Lens', over, view, (%~), (.~))
import Data.Bifoldable (binull)
import Data.Bitraversable (bitraverse)
import Data.List qualified as List
import Data.Map.Strict qualified as Map
import Data.Semialign (alignWith)
import Data.These (These (..))
import Unison.ConstructorReference (GConstructorReference (..))
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.DeclNameLookup (DeclNameLookup (..), expectConstructorNames, expectDeclName)
import Unison.Merge.DiffOp (DiffOp (..))
import Unison.Merge.Synhashed (Synhashed (..))
import Unison.Merge.TwoDiffOps (TwoDiffOps (..), combineTwoDiffOps)
@ -26,158 +24,60 @@ 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 (TermReference, TermReferenceId, TypeReference, TypeReferenceId)
import Unison.Reference qualified as Reference
import Unison.Referent (Referent)
import Unison.Referent qualified as 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)
import Unison.Util.Map qualified as Map
import Unison.Util.Defns (Defns (..), DefnsF, DefnsF2, DefnsF3, DefnsF4)
-- | The combined result of two diffs on the same thing.
data DiffOp2 v
= Added2 !AliceIorBob !v
| Updated2 !AliceIorBob !v -- new value
| Deleted2 !AliceIorBob !v -- old value
| -- | An add-add or an update-update conflict. We don't consider update-delete a conflict; the delete gets ignored.
Conflict !(TwoWay v)
data Flicts v = Flicts
{ conflicts :: !(Map Name (TwoWay v)),
unconflicts :: !(Unconflicts v)
}
deriving stock (Generic)
data CombinedDiffOps ref
= CombinedDiffOps'Add !AliceIorBob !ref
| CombinedDiffOps'Delete !AliceIorBob !ref -- old value
| CombinedDiffOps'Update !AliceIorBob !ref !ref -- 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)
-- | Combine LCA->Alice diff and LCA->Bob diff, then partition into conflicted and unconflicted things.
combineDiffs ::
TwoWay DeclNameLookup ->
TwoWay (Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name)) ->
TwoWay (DefnsF (Map Name) (DiffOp (Synhashed Referent)) (DiffOp (Synhashed TypeReference))) ->
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 honk =
oingoBoingo
declNameLookups
defns
( bimap
(twoWay (alignWith combine'terms))
(twoWay (Map.alignWithKey (combine'types declNameLookups)))
(TwoWay.sequenceDefns diffs)
)
let diffs1 :: DefnsF4 TwoWay (Map Name) DiffOp Synhashed Referent TypeReference
diffs1 =
TwoWay.sequenceDefns diffs
let (termConflicts0, termUnconflicts0) = partition2 (TwoWay.justTheTerms diffs)
(typeConflicts0, typeUnconflicts0) = partition2 (TwoWay.justTheTypes diffs)
let diffs2 :: DefnsF2 (Map Name) CombinedDiffOps Referent TypeReference
diffs2 =
let f = twoWay (alignWith combine)
in bimap f f diffs1
let initialConflicts :: TwoWay (DefnsF (Map Name) TermReference TypeReference)
initialConflicts =
twiddleTermConflicts declNameLookups termConflicts0 <> justTypes (sequenceA typeConflicts0)
let conflicts0 = identifyConflicts declNameLookups defns diffs2
let moreTermConflicts :: TwoWay (DefnsF (Map Name) TermReference TypeReference)
moreTermConflicts =
justTerms (discoverTermConflicts declNameLookups (view #terms <$> defns) (TwoWay.justTheTypes initialConflicts))
let unconflicts = identifyUnconflicts declNameLookups conflicts0 diffs2
conflicts <- assertThereAreNoBuiltins (initialConflicts <> moreTermConflicts)
conflicts <- assertThereAreNoBuiltins conflicts0
let termUnconflicts = dropConflictedUnconflictedTerms declNameLookups conflicts termUnconflicts0
let typeUnconflicts = dropConflictedUnconflictedTypes (TwoWay.justTheTypes conflicts) typeUnconflicts0
Right (conflicts, unconflicts)
Right (conflicts, Defns termUnconflicts typeUnconflicts)
oingoBoingo ::
identifyConflicts ::
TwoWay DeclNameLookup ->
TwoWay (Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name)) ->
Defns (Map Name (DiffOp2 Referent)) (Map Name (DiffOp2 TypeReference)) ->
DefnsF2 (Map Name) CombinedDiffOps Referent TypeReference ->
TwoWay (DefnsF (Map Name) TermReference TypeReference)
oingoBoingo declNameLookups defns diff =
let initialQueues :: TwoWay (DefnsF [] Name Name)
initialQueues =
bitraverse honkTerms honkTypes diff
-- added term: we keep the add
--
-- added constructor:
-- - if its type is me-conflicted then we drop the add
-- - else if its type is them-conflicted then we promote our type to me-conflicted
-- - example:
-- LCA: type Foo = Foo | Bar
-- Alice: type Foo = Foo | Bar | Baz
-- Bob: type Foo = Foo | Oink
--
-- Alice diff:
-- updated type Foo
-- updated con Foo.Bar
-- updated con Foo.Baz
--
-- Bob diff:
-- deleted con Foo.Bar
-- added con Foo.Oink
--
-- in this example, "Foo.Oink" is added in Bob, "Foo" is not conflicted in Bob, but
--
--
-- after finding an initial batch of type conflicts,
--
-- 1. some constructor adds/updates will be for types that are conflicted. we can drop these!
-- 2. others will be for types that are updates, but aren't conflicted. we keep these!
-- 3. others will be for types that haven't been updated, but we only keep these if the other person's type isn't
-- conflicted or updated; if it is, then our changes to the type's constructor essentially promote the type to
-- conflicted! uh oh! this can cascade... so what order do we do these checks in?
honkTerms :: Map Name (DiffOp2 term) -> TwoWay [Name]
honkTerms =
Map.foldlWithKey' f (TwoWay [] [])
where
f :: TwoWay [Name] -> Name -> DiffOp2 term -> TwoWay [Name]
f acc name = \case
Conflict _ -> (name :) <$> acc
-- FIXME are these right? just dealing with conflicts for now, then will circle back and think
Added2 _ _ -> acc
Deleted2 _ _ -> acc
Updated2 _ _ -> acc
-- FIXME same as honkTerms, forever tho?
honkTypes :: Map Name (DiffOp2 typ) -> TwoWay [Name]
honkTypes =
Map.foldlWithKey' f (TwoWay [] [])
where
f :: TwoWay [Name] -> Name -> DiffOp2 typ -> TwoWay [Name]
f acc name = \case
Conflict _ -> (name :) <$> acc
-- FIXME are these right? just dealing with conflicts for now, then will circle back and think
Added2 _ _ -> acc
Deleted2 _ _ -> acc
Updated2 _ _ -> acc
in boingo2
declNameLookups
defns
S
{ me = Alice,
conflicts = mempty,
stacks = initialQueues
}
data S = S
{ me :: !AliceXorBob,
conflicts :: !(TwoWay (DefnsF (Map Name) TermReference TypeReference)),
stacks :: !(TwoWay (DefnsF [] Name Name))
}
deriving stock (Generic)
boingo2 ::
TwoWay DeclNameLookup ->
TwoWay (Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name)) ->
S ->
TwoWay (DefnsF (Map Name) TermReference TypeReference)
boingo2 declNameLookups defns =
loop
identifyConflicts declNameLookups defns =
\diff -> loop (makeInitialIdentifyConflictsState diff)
where
loop :: S -> TwoWay (DefnsF (Map Name) TermReference TypeReference)
loop s =
@ -187,12 +87,14 @@ boingo2 declNameLookups defns =
([], [], 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 :)
poppedType :: Name -> S -> S
poppedType name =
case BiMultimap.lookupRan name (view (me_ . #types) defns) of
Nothing -> id
@ -235,214 +137,153 @@ boingo2 declNameLookups defns =
theirTermStack_ :: Lens' S [Name]
theirTermStack_ = theirStacks_ . #terms
partition2 :: TwoWay (Map Name (DiffOp (Synhashed v))) -> (Map Name (TwoWay v), Unconflicts v)
partition2 =
partition . twoWay (alignWith (combine Alice Bob))
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))
partition :: Map Name (DiffOp2 v) -> (Map Name (TwoWay v), Unconflicts v)
partition =
Map.foldlWithKey'
(\s k v -> insert k v s)
( Map.empty,
let empty = TwoWayI Map.empty Map.empty Map.empty
in Unconflicts empty empty empty
)
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
insert :: Name -> DiffOp2 v -> (Map Name (TwoWay v), Unconflicts v) -> (Map Name (TwoWay v), Unconflicts v)
insert k = \case
Conflict v -> _1 %~ Map.insert k v
Added2 who v -> _2 . #adds . TwoWayI.who_ who %~ Map.insert k v
Deleted2 who v -> _2 . #deletes . TwoWayI.who_ who %~ Map.insert k v
Updated2 who v -> _2 . #updates . TwoWayI.who_ who %~ Map.insert k v
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
combine :: AliceXorBob -> AliceXorBob -> These (DiffOp (Synhashed v)) (DiffOp (Synhashed v)) -> DiffOp2 v
combine this that = \case
This x -> one this x
That x -> one that x
These (DiffOp'Add x) (DiffOp'Add y)
| x /= y -> Conflict (two this x.value y.value)
| otherwise -> Added2 AliceAndBob x.value
These (DiffOp'Update _ x) (DiffOp'Update _ y)
| x /= y -> Conflict (two this x.value y.value)
| otherwise -> Updated2 AliceAndBob x.value
-- Not a conflict, perhaps only temporarily, because it's easier to implement (we ignore these deletes):
These (DiffOp'Update _ x) (DiffOp'Delete _) -> Updated2 (xor2ior this) x.value
These (DiffOp'Delete x) (DiffOp'Delete _) -> Deleted2 AliceAndBob x.value
-- Handle delete+update the same as update+delete
These x@(DiffOp'Delete _) y -> combine that this (These y x)
-- These don't make sense - e.g. someone can't update something that wasn't there
These (DiffOp'Update _ _) (DiffOp'Add _) -> error "impossible"
These (DiffOp'Add _) (DiffOp'Delete _) -> error "impossible"
These (DiffOp'Add _) (DiffOp'Update _ _) -> error "impossible"
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
one :: AliceXorBob -> DiffOp (Synhashed v) -> DiffOp2 v
one who = \case
DiffOp'Add x -> Added2 (xor2ior who) x.value
DiffOp'Delete x -> Deleted2 (xor2ior who) x.value
DiffOp'Update _ x -> Updated2 (xor2ior who) x.value
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
-- Make a two way, given who is on the left.
two :: AliceXorBob -> v -> v -> TwoWay v
two Alice alice bob = TwoWay {alice, bob}
two Bob bob alice = TwoWay {alice, bob}
typeIsConflicted :: TwoWay Bool
typeIsConflicted =
Map.member name <$> conflicts
combine'terms :: These (DiffOp (Synhashed term)) (DiffOp (Synhashed term)) -> DiffOp2 term
combine'terms =
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 =
combineTwoDiffOps >>> \case
TwoDiffOps'Add who x -> Added2 (xor2ior who) x.value
TwoDiffOps'Delete who x -> Deleted2 (xor2ior who) x.value
TwoDiffOps'Update who _old new -> Updated2 (xor2ior who) new.value
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'AddAdd TwoWay {alice, bob}
| alice /= bob -> Conflict TwoWay {alice = alice.value, bob = bob.value}
| otherwise -> Added2 AliceAndBob alice.value
TwoDiffOps'DeleteDelete x -> Deleted2 AliceAndBob x.value
| 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
-- 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 -> Updated2 OnlyBob new.value
TwoDiffOps'UpdateDelete _old new -> Updated2 OnlyAlice new.value
TwoDiffOps'UpdateUpdate _old TwoWay {alice, bob}
| alice /= bob -> Conflict TwoWay {alice = alice.value, bob = bob.value}
| otherwise -> Updated2 AliceAndBob alice.value
combine'types ::
TwoWay DeclNameLookup ->
Name ->
These (DiffOp (Synhashed typ)) (DiffOp (Synhashed typ)) ->
DiffOp2 typ
combine'types declNameLookups name =
combineTwoDiffOps >>> \case
TwoDiffOps'Add who x -> Added2 (xor2ior who) x.value
TwoDiffOps'Delete who x -> Deleted2 (xor2ior who) x.value
-- Treat one person updating a type and the other just moving constructors around as a conflict
TwoDiffOps'Update who old new
| differentNamesForConstructors ->
TwoWay {alice = new.value, bob = old.value}
& (case who of Alice -> id; Bob -> TwoWay.swap)
& Conflict
| otherwise -> Updated2 (xor2ior who) new.value
TwoDiffOps'AddAdd TwoWay {alice, bob}
| conflicting alice bob -> Conflict TwoWay {alice = alice.value, bob = bob.value}
| otherwise -> Added2 AliceAndBob alice.value
TwoDiffOps'DeleteDelete x -> Deleted2 AliceAndBob x.value
TwoDiffOps'DeleteUpdate _old new -> Updated2 OnlyBob new.value
TwoDiffOps'UpdateDelete _old new -> Updated2 OnlyAlice new.value
TwoDiffOps'UpdateUpdate _old TwoWay {alice, bob}
| conflicting alice bob -> Conflict TwoWay {alice = alice.value, bob = bob.value}
| otherwise -> Updated2 AliceAndBob alice.value
where
-- We consider type decls in conflict if they are different (obviously) *or* if they don't have the exact same names
-- for all of the constructors.
--
-- This is, in a sense, a conservative definition of "conflicted" - surely Alice ought to be able to rename one
-- constructor, and Bob another.
--
-- However, it simplifies two cases, and possibly more, to just throw this kind of thing back to the user to
-- resolve:
--
-- 1. Alice and Bob each rename the same constructor to two different things, resulting in a decl that violates
-- the condition that each constructor has one name.
--
-- 2. Alice updates a type decl while Bob merely renames one of its constructors.
conflicting :: Synhashed typ -> Synhashed typ -> Bool
conflicting alice bob =
alice /= bob || differentNamesForConstructors
differentNamesForConstructors :: Bool
differentNamesForConstructors =
expectConstructorNames declNameLookups.alice name /= expectConstructorNames declNameLookups.bob name
TwoDiffOps'DeleteUpdate old new -> CombinedDiffOps'Update OnlyBob old.value new.value
TwoDiffOps'UpdateDelete old new -> CombinedDiffOps'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
xor2ior :: AliceXorBob -> AliceIorBob
xor2ior = \case
Alice -> OnlyAlice
Bob -> OnlyBob
------------------------------------------------------------------------------------------------------------------------
-- Conflict twiddling
-- returned maps don't necessarily have the same keys, e.g. if incoming conflict is
--
-- {
-- terms = {
-- "Maybe.Just" => {
-- alice = #Alice#0,
-- bob = #bob
-- }
-- }
-- }
--
-- (where Alice has a constructor and Bob has a term), then the outgoing maps will be
--
-- {
-- alice = {
-- types = {
-- "Maybe" => #Alice
-- }
-- },
-- bob = {
-- terms = {
-- "Maybe.Just" => #bob
-- }
-- }
-- }
twiddleTermConflicts ::
TwoWay DeclNameLookup ->
Map Name (TwoWay Referent) ->
TwoWay (DefnsF (Map Name) TermReference TypeReference)
twiddleTermConflicts declNameLookups =
Map.foldlWithKey' f (let empty = Defns Map.empty Map.empty in TwoWay empty empty)
where
f ::
TwoWay (DefnsF (Map Name) TermReference TypeReference) ->
Name ->
TwoWay Referent ->
TwoWay (DefnsF (Map Name) TermReference TypeReference)
f acc name referents =
twiddleTermConflict name <$> declNameLookups <*> referents <*> acc
twiddleTermConflict ::
Name ->
DeclNameLookup ->
Referent ->
DefnsF (Map Name) TermReference TypeReference ->
DefnsF (Map Name) TermReference TypeReference
twiddleTermConflict name declNameLookup = \case
Referent'.Con' (ConstructorReference ref _) _ -> over #types (Map.insert (expectDeclName declNameLookup name) ref)
Referent'.Ref' ref -> over #terms (Map.insert name ref)
discoverTermConflicts ::
TwoWay DeclNameLookup ->
TwoWay (BiMultimap Referent Name) ->
TwoWay (Map Name TypeReference) ->
TwoWay (Map Name TermReference)
discoverTermConflicts declNameLookups terms typeConflicts =
TwoWay.swap (f <$> declNameLookups <*> typeConflicts <*> TwoWay.swap terms)
where
f ::
DeclNameLookup ->
Map Name TypeReference ->
BiMultimap Referent Name ->
Map Name TermReference
f myDeclNameLookup myTypeConflicts theirTerms =
Map.foldlWithKey' (g myDeclNameLookup theirTerms) Map.empty myTypeConflicts
g ::
DeclNameLookup ->
BiMultimap Referent Name ->
Map Name TermReference ->
Name ->
TypeReference ->
Map Name TermReference
g myDeclNameLookup theirTerms acc myDeclName _ =
List.foldl' (h theirTerms) acc (expectConstructorNames myDeclNameLookup myDeclName)
h :: BiMultimap Referent Name -> Map Name TermReference -> Name -> Map Name TermReference
h theirTerms acc myConName =
fromMaybe acc do
theirReferent <- BiMultimap.lookupRan myConName theirTerms
theirTerm <- Referent.toTermReference theirReferent
Just (Map.insert myConName theirTerm acc)
assertThereAreNoBuiltins ::
TwoWay (DefnsF (Map Name) TermReference TypeReference) ->
Either Name (TwoWay (DefnsF (Map Name) TermReferenceId TypeReferenceId))
@ -461,55 +302,3 @@ assertThereAreNoBuiltins =
case Reference.toId ref of
Nothing -> Left name
Just refId -> Right refId
------------------------------------------------------------------------------------------------------------------------
-- Unconflict twiddling
dropConflictedUnconflictedTerms ::
TwoWay DeclNameLookup ->
TwoWay (DefnsF (Map Name) TermReferenceId TypeReferenceId) ->
Unconflicts Referent ->
Unconflicts Referent
dropConflictedUnconflictedTerms declNameLookups conflicts =
over #adds f . over #updates f
where
f :: TwoWayI (Map Name Referent) -> TwoWayI (Map Name Referent)
f =
over #alice (Map.filterWithKey aliceIsConflicted)
. over #bob (Map.filterWithKey bobIsConflicted)
. over #both (Map.filterWithKey \name ref -> aliceIsConflicted name ref || bobIsConflicted name ref)
where
isConflicted :: DeclNameLookup -> DefnsF (Map Name) TermReferenceId TypeReferenceId -> Name -> Referent -> Bool
isConflicted declNameLookup conflicts name = \case
Referent'.Con' _ _ -> Map.notMember (expectDeclName declNameLookup name) conflicts.types
Referent'.Ref' _ -> Map.notMember name conflicts.terms
aliceIsConflicted = isConflicted declNameLookups.alice conflicts.alice
bobIsConflicted = isConflicted declNameLookups.bob conflicts.bob
dropConflictedUnconflictedTypes ::
TwoWay (Map Name types) ->
Unconflicts TypeReference ->
Unconflicts TypeReference
dropConflictedUnconflictedTypes conflicts =
over #adds f . over #updates f
where
f :: TwoWayI (Map Name Reference) -> TwoWayI (Map Name Reference)
f =
over #alice dropAliceConflicted
. over #bob dropBobConflicted
. over #both (dropAliceConflicted . dropBobConflicted)
where
dropAliceConflicted = (`Map.difference` conflicts.alice)
dropBobConflicted = (`Map.difference` conflicts.bob)
------------------------------------------------------------------------------------------------------------------------
-- Misc. helpers
justTerms :: TwoWay (Map name terms) -> TwoWay (DefnsF (Map name) terms types)
justTerms =
fmap (\terms -> Defns terms Map.empty)
justTypes :: TwoWay (Map name types) -> TwoWay (DefnsF (Map name) terms types)
justTypes =
fmap (\types -> Defns Map.empty types)

View File

@ -34,7 +34,7 @@ import Unison.Syntax.Name qualified as Name
import Unison.Type (Type)
import Unison.Util.BiMultimap (BiMultimap)
import Unison.Util.BiMultimap qualified as BiMultimap
import Unison.Util.Defns (Defns (..), DefnsF)
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:
@ -51,14 +51,7 @@ nameBasedNamespaceDiff ::
MergeDatabase ->
ThreeWay DeclNameLookup ->
ThreeWay (Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name)) ->
Transaction
( TwoWay
( DefnsF
(Map Name)
(DiffOp (Synhashed Referent))
(DiffOp (Synhashed TypeReference))
)
)
Transaction (TwoWay (DefnsF3 (Map Name) DiffOp Synhashed Referent TypeReference))
nameBasedNamespaceDiff db declNameLookups defns = do
diffs <- sequence (synhashDefns <$> declNameLookups <*> defns)
pure (diffNamespaceDefns diffs.lca <$> TwoWay {alice = diffs.alice, bob = diffs.bob})
@ -66,7 +59,7 @@ nameBasedNamespaceDiff db declNameLookups defns = do
synhashDefns ::
DeclNameLookup ->
Defns (BiMultimap Referent Name) (BiMultimap TypeReference Name) ->
Transaction (DefnsF (Map Name) (Synhashed Referent) (Synhashed TypeReference))
Transaction (DefnsF2 (Map Name) Synhashed Referent TypeReference)
synhashDefns declNameLookup =
-- FIXME: use cache so we only synhash each thing once
synhashDefnsWith
@ -101,26 +94,23 @@ withAccurateConstructorNames load declNameLookup name ref = do
(expectConstructorNames declNameLookup name)
diffNamespaceDefns ::
DefnsF (Map Name) (Synhashed Referent) (Synhashed TypeReference) ->
DefnsF (Map Name) (Synhashed Referent) (Synhashed TypeReference) ->
DefnsF (Map Name) (DiffOp (Synhashed Referent)) (DiffOp (Synhashed TypeReference))
diffNamespaceDefns oldDefns newDefns =
Defns
{ terms = go oldDefns.terms newDefns.terms,
types = go oldDefns.types newDefns.types
}
DefnsF2 (Map Name) Synhashed term typ ->
DefnsF2 (Map Name) Synhashed term typ ->
DefnsF3 (Map Name) DiffOp Synhashed term typ
diffNamespaceDefns =
zipDefnsWith f f
where
go :: Map Name (Synhashed ref) -> Map Name (Synhashed ref) -> Map Name (DiffOp (Synhashed ref))
go old new =
Map.mapMaybe id (alignWith f old new)
where
f :: Eq x => These x x -> Maybe (DiffOp x)
f = \case
This x -> Just (DiffOp'Delete x)
That y -> Just (DiffOp'Add y)
These x y
| x == y -> Nothing
| otherwise -> Just (DiffOp'Update x y)
f :: Map Name (Synhashed ref) -> Map Name (Synhashed ref) -> Map Name (DiffOp (Synhashed ref))
f old new =
Map.mapMaybe id (alignWith g old new)
g :: Eq x => These x x -> Maybe (DiffOp x)
g = \case
This x -> Just (DiffOp'Delete x)
That y -> Just (DiffOp'Add y)
These x y
| x == y -> Nothing
| otherwise -> Just (DiffOp'Update x y)
------------------------------------------------------------------------------------------------------------------------
-- Pretty-print env helpers
@ -145,7 +135,7 @@ synhashDefnsWith ::
(term -> m Hash) ->
(Name -> typ -> m Hash) ->
Defns (BiMultimap term Name) (BiMultimap typ Name) ->
m (DefnsF (Map Name) (Synhashed term) (Synhashed typ))
m (DefnsF2 (Map Name) Synhashed term typ)
synhashDefnsWith hashTerm hashType = do
bitraverse
(traverse hashTerm1 . BiMultimap.range)

View File

@ -1,7 +1,9 @@
module Unison.Merge.TwoWay
( TwoWay (..),
bothWays,
justTheTerms,
justTheTypes,
or,
sequenceDefns,
swap,
twoWay,
@ -17,8 +19,8 @@ import Data.These (These (These))
import Data.Zip (Zip, unzipWith, zipWith)
import Unison.Merge.AliceXorBob (AliceXorBob (..))
import Unison.Prelude
import Unison.Util.Defns (Defns (..))
import Prelude hiding (zipWith)
import Unison.Util.Defns (Defns (..), DefnsF)
import Prelude hiding (or, zipWith)
data TwoWay a = TwoWay
{ alice :: a,
@ -41,6 +43,10 @@ instance Zip TwoWay where
zipWith f (TwoWay x1 x2) (TwoWay y1 y2) =
TwoWay (f x1 y1) (f x2 y2)
bothWays :: a -> TwoWay a
bothWays x =
TwoWay x x
justTheTerms :: TwoWay (Defns terms types) -> TwoWay terms
justTheTerms =
fmap (view #terms)
@ -49,7 +55,11 @@ justTheTypes :: TwoWay (Defns terms types) -> TwoWay types
justTheTypes =
fmap (view #types)
sequenceDefns :: TwoWay (Defns terms types) -> Defns (TwoWay terms) (TwoWay types)
or :: TwoWay Bool -> Bool
or =
twoWay (||)
sequenceDefns :: TwoWay (Defns terms types) -> DefnsF TwoWay terms types
sequenceDefns defns =
Defns (justTheTerms defns) (justTheTypes defns)

View File

@ -2,12 +2,14 @@
module Unison.Merge.Unconflicts
( Unconflicts (..),
empty,
)
where
import Unison.Merge.TwoWayI (TwoWayI)
import Data.Map.Strict qualified as Map
import Unison.Merge.TwoWayI (TwoWayI (..))
import Unison.Name (Name)
import Unison.Prelude
import Unison.Prelude hiding (empty)
data Unconflicts v = Unconflicts
{ adds :: !(TwoWayI (Map Name v)),
@ -15,3 +17,9 @@ data Unconflicts v = Unconflicts
updates :: !(TwoWayI (Map Name v))
}
deriving stock (Foldable, Functor, Generic)
empty :: Unconflicts v
empty =
Unconflicts x x x
where
x = TwoWayI Map.empty Map.empty Map.empty