don't expect constructors of builtin types

This commit is contained in:
Mitchell Rosen 2024-04-17 18:24:28 -04:00
parent ad1c9b9f98
commit 0299f38946
4 changed files with 1428 additions and 16 deletions

View File

@ -26,7 +26,7 @@ import Unison.Merge.Unconflicts (Unconflicts (..))
import Unison.Merge.Unconflicts qualified as Unconflicts
import Unison.Name (Name)
import Unison.Prelude hiding (catMaybes)
import Unison.Reference (TermReference, TermReferenceId, TypeReference, TypeReferenceId)
import Unison.Reference (Reference' (..), TermReference, TermReferenceId, TypeReference, TypeReferenceId)
import Unison.Reference qualified as Reference
import Unison.Referent (Referent)
import Unison.Referent qualified as Referent
@ -72,12 +72,13 @@ combineDiffs declNameLookups defns diffs = do
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 =
\diff -> loop (makeInitialIdentifyConflictsState diff)
loop . makeInitialIdentifyConflictsState
where
loop :: S -> TwoWay (DefnsF (Map Name) TermReference TypeReference)
loop s =
@ -102,7 +103,9 @@ identifyConflicts declNameLookups defns =
Just $
s
& myTypeConflicts_ .~ conflicts
& theirTermStack_ %~ (expectConstructorNames myDeclNameLookup name ++)
& 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

View File

@ -40,13 +40,13 @@ data DeclNameLookup = DeclNameLookup
deriving stock (Generic)
deriving (Semigroup) via (GenericSemigroupMonoid DeclNameLookup)
expectDeclName :: DeclNameLookup -> Name -> Name
expectDeclName :: HasCallStack => DeclNameLookup -> Name -> Name
expectDeclName DeclNameLookup {constructorToDecl} x =
case Map.lookup x constructorToDecl of
Nothing -> error (reportBug "E246726" ("Expected constructor name key " <> show x <> " in decl name lookup"))
Just y -> y
expectConstructorNames :: DeclNameLookup -> Name -> [Name]
expectConstructorNames :: HasCallStack => DeclNameLookup -> Name -> [Name]
expectConstructorNames DeclNameLookup {declToConstructors} x =
case Map.lookup x declToConstructors of
Nothing -> error (reportBug "E077058" ("Expected decl name key " <> show x <> " in decl name lookup"))

View File

@ -1,6 +1,422 @@
# tests for the new merge command
# The `merge` command
## Basic fast-forward merge
The `merge` command merges together two branches in the same project: the current branch (unspecificed), and the target
branch. For example, to merge `topic` into `main`, switch to `main` and run `merge topic`. Let's see a simple,
unconflicted merge in action, wherein Alice (us) and Bob (them) have added different terms.
## Basic merge
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch alice
```
```unison
foo : Text
foo = "alices foo"
```
```ucm
project/alice> add
project/main> branch bob
```
```unison
bar : Text
bar = "bobs bar"
```
```ucm
project/bob> add
project/alice> merge2 /bob
project/alice> view foo bar
```
```ucm:hide
.> project.delete project
```
## Update propagation
Updates are propagated. In this example, Alice updates `foo`, and Bob adds a new dependent `bar` of (the old) `foo`.
When Bob's branch is merged into Alice's, her update to `foo` is propagated to `bar`.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Text
foo = "old foo"
```
```ucm
project/main> add
project/main> branch alice
```
```unison
foo : Text
foo = "new foo"
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
bar : Text
bar = foo ++ foo
```
```ucm
project/bob> add
project/alice> merge2 /bob
project/alice> view foo bar
```
```ucm:hide
.> project.delete project
```
## Update propagation with common dependent
Different hashes don't necessarily imply an update. In this example, Alice and Bob both update different dependencies
`bar` and `baz` of a common dependent `foo`, so their `foo`s have different hashes. However, we can merge these changes
together just fine, resulting in a `foo` that incorporates both updates.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Text
foo = "foo" ++ bar ++ baz
bar : Text
bar = "old bar"
baz : Text
baz = "old baz"
```
```ucm
project/main> add
project/main> branch alice
```
```unison
bar : Text
bar = "alices bar"
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
baz : Text
baz = "bobs baz"
```
```ucm
project/bob> update
project/alice> merge2 /bob
project/alice> view foo bar baz
```
```ucm:hide
.> project.delete project
```
## Typechecking failure
Alice's update may fail to typecheck when propagating to Bob's dependents.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Text
foo = "foo"
```
```ucm
project/main> add
project/main> branch alice
```
```unison
foo : Nat
foo = 100
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
bar : Text
bar = foo ++ foo
```
```ucm:error
project/bob> update
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
## Simple term conflict
Alice and Bob may disagree about the definition of a term. In this case, the conflicted term and all of its dependents
are given to the user to resolve. The unconflicted parts of a merge (and any merge with conflicts in general) are put
into the namespace.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Text
foo = "old foo"
bar : Text
bar = "old bar"
```
```ucm
project/main> add
project/main> branch alice
```
```unison
foo : Text
foo = "alices foo"
bar : Text
bar = "alices bar"
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
foo : Text
foo = "bobs foo"
baz : Text
baz = "bobs baz"
```
```ucm:error
project/bob> update
project/alice> merge2 /bob
```
```ucm
project/merge-bob-into-alice> view bar baz
```
```ucm:hide
.> project.delete project
```
## Simple type conflict
Ditto for types; if the hashes don't match, it's a conflict. In this example, Alice and Bob do different things to the
same constructor. However, any explicit changes to the same type will result in a conflict, including changes that could
concievably be merged (e.g. Alice and Bob both add a new constructor, or edit different constructors).
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
unique type Foo = MkFoo Nat
```
```ucm
project/main> add
project/main> branch alice
```
```unison
unique type Foo = MkFoo Nat Nat
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
unique type Foo = MkFoo Nat Text
```
```ucm:error
project/bob> update
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
## 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
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.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
unique type Foo
= MkFooOne Nat
| MkFooTwo Nat Nat
```
```ucm
project/main> add
project/main> branch alice
```
```unison
unique type Foo
= MkFooOne Nat Text
| MkFooTwo Nat Nat
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
unique type Foo
= MkFooOne Nat
| MkFooTwoRenamed Nat Nat
Foo.MkFooTwo : Text
Foo.MkFooTwo = "hello"
```
```ucm:error
project/bob> update
project/alice> merge2 /bob
```
```ucm:hide
.> 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
user will have to fix up the namespace(s) manually before attempting to merge again.
### Conflicted aliases
If `foo` and `bar` are aliases in the nearest common ancestor, but not in Alice's branch, then we don't know whether to
update Bob's dependents to Alice's `foo` or Alice's `bar` (and vice-versa).
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Nat
foo = 100
bar : Nat
bar = 100
```
```ucm
project/main> add
project/main> branch alice
```
```unison
foo : Nat
foo = 200
bar : Nat
bar = 300
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
baz : Text
baz = "baz"
```
```ucm:error
project/bob> add
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
### Conflict involving builtin
We don't have a way of rendering a builtin in a scratch file, where users resolve merge conflicts. Thus, if there is a
conflict involving a builtin, we can't perform a merge.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch topic
project/main> alias.type builtin.Nat MyNat
project/topic>
```
```unison
unique type MyNat = MyNat Nat
```
```ucm:error
project/topic> add
project/main> merge2 /topic
```
```ucm:hide
.> project.delete project
```
# fast-forward
```ucm:hide
.> project.create-empty proj
@ -15,7 +431,7 @@ foo = 1
```ucm
proj/main> add
proj/main> branch topic
proj/topic>
proj/topic>
```
```unison
@ -49,7 +465,7 @@ foo = 1
```ucm
proj/topic> add
proj/main>
proj/main>
```
```unison
@ -85,7 +501,7 @@ foo = 1
```ucm
proj/topic> add
proj/main>
proj/main>
```
```unison
@ -353,7 +769,7 @@ foo = 1
```ucm
proj/main> add
proj/main> branch topic
proj/topic>
proj/topic>
```
`topic` adds a dependent of `foo`

File diff suppressed because it is too large Load Diff