Skip to content

Commit 68d6f93

Browse files
authored
Merge pull request #4388 from unisonweb/fix/no-crash-findCtorNames
2 parents ece467b + a5a9c0a commit 68d6f93

File tree

9 files changed

+209
-62
lines changed

9 files changed

+209
-62
lines changed

unison-cli/src/Unison/Cli/Monad.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ runTransaction action = do
417417
liftIO (Codebase.runTransaction codebase action)
418418

419419
-- | Run a transaction that can abort early with an output message.
420+
-- todo: rename to runTransactionWithReturnEarly
420421
runTransactionWithRollback :: ((forall void. Output -> Sqlite.Transaction void) -> Sqlite.Transaction a) -> Cli a
421422
runTransactionWithRollback action = do
422423
Env {codebase} <- ask

unison-cli/src/Unison/Codebase/Editor/HandleInput/Update2.hs

Lines changed: 62 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module Unison.Codebase.Editor.HandleInput.Update2
1414
where
1515

1616
import Control.Lens (over, (^.))
17+
import Control.Lens qualified as Lens
1718
import Control.Monad.RWS (ask)
1819
import Data.Foldable qualified as Foldable
1920
import Data.List.NonEmpty qualified as NonEmpty
@@ -35,6 +36,7 @@ import Unison.Codebase.Branch qualified as Branch
3536
import Unison.Codebase.Branch.Names qualified as Branch
3637
import Unison.Codebase.Branch.Type (Branch0)
3738
import Unison.Codebase.BranchUtil qualified as BranchUtil
39+
import Unison.Codebase.Editor.Output (Output)
3840
import Unison.Codebase.Editor.Output qualified as Output
3941
import Unison.Codebase.Path (Path)
4042
import Unison.Codebase.Path qualified as Path
@@ -64,6 +66,7 @@ import Unison.Prelude
6466
import Unison.PrettyPrintEnvDecl (PrettyPrintEnvDecl)
6567
import Unison.PrettyPrintEnvDecl qualified as PPED
6668
import Unison.PrettyPrintEnvDecl.Names qualified as PPE
69+
import Unison.Reference (TypeReferenceId)
6770
import Unison.Reference qualified as Reference (fromId)
6871
import Unison.Referent (Referent)
6972
import Unison.Referent qualified as Referent
@@ -77,6 +80,7 @@ import Unison.Type (Type)
7780
import Unison.UnisonFile qualified as UF
7881
import Unison.UnisonFile.Names qualified as UF
7982
import Unison.UnisonFile.Type (TypecheckedUnisonFile, UnisonFile)
83+
import Unison.Util.Monoid qualified as Monoid
8084
import Unison.Util.Nametree (Defns (..))
8185
import Unison.Util.Pretty (Pretty)
8286
import Unison.Util.Pretty qualified as Pretty
@@ -98,14 +102,14 @@ handleUpdate2 = do
98102
let ctorNames = forwardCtorNames namesExcludingLibdeps
99103

100104
Cli.respond Output.UpdateLookingForDependents
101-
(pped, bigUf) <- Cli.runTransaction do
105+
(pped, bigUf) <- Cli.runTransactionWithRollback \abort -> do
102106
dependents <-
103107
Ops.dependentsWithinScope
104108
(Names.referenceIds namesExcludingLibdeps)
105109
(getExistingReferencesNamed termAndDeclNames namesExcludingLibdeps)
106110
-- - construct PPE for printing UF* for typechecking (whatever data structure we decide to print)
107111
pped <- Codebase.hashLength <&> (`PPE.fromNamesDecl` (NamesWithHistory.fromCurrentNames namesIncludingLibdeps))
108-
bigUf <- buildBigUnisonFile codebase tuf dependents namesExcludingLibdeps ctorNames
112+
bigUf <- buildBigUnisonFile abort codebase tuf dependents namesExcludingLibdeps ctorNames
109113
let tufPped = PPE.fromNamesDecl 8 (Names.NamesWithHistory (UF.typecheckedToNames tuf) mempty)
110114

111115
pure (pped `PPED.addFallback` tufPped, bigUf)
@@ -161,16 +165,14 @@ makeParsingEnv path names = do
161165
}
162166

163167
-- save definitions and namespace
164-
saveTuf :: (Name -> [Name]) -> TypecheckedUnisonFile Symbol Ann -> Cli ()
168+
saveTuf :: (Name -> Either Output [Name]) -> TypecheckedUnisonFile Symbol Ann -> Cli ()
165169
saveTuf getConstructors tuf = do
166170
Cli.Env {codebase} <- ask
167171
currentPath <- Cli.getCurrentPath
168-
Cli.runTransaction $ Codebase.addDefsToCodebase codebase tuf
169-
Cli.stepAt
170-
"update"
171-
( Path.unabsolute currentPath,
172-
Branch.batchUpdates (typecheckedUnisonFileToBranchUpdates getConstructors tuf)
173-
)
172+
branchUpdates <- Cli.runTransactionWithRollback \abort -> do
173+
Codebase.addDefsToCodebase codebase tuf
174+
typecheckedUnisonFileToBranchUpdates abort getConstructors tuf
175+
Cli.stepAt "update" (Path.unabsolute currentPath, Branch.batchUpdates branchUpdates)
174176

175177
-- @typecheckedUnisonFileToBranchUpdates getConstructors file@ returns a list of branch updates (suitable for passing
176178
-- along to `batchUpdates` or some "step at" combinator) that corresponds to using all of the contents of @file@.
@@ -184,28 +186,30 @@ saveTuf getConstructors tuf = do
184186
--
185187
-- [ ("foo.bar", insert-term("baz",<#foo>)) ]
186188
typecheckedUnisonFileToBranchUpdates ::
187-
(Name -> [Name]) ->
188-
TypecheckedUnisonFile Symbol a ->
189-
[(Path, Branch0 m -> Branch0 m)]
190-
typecheckedUnisonFileToBranchUpdates getConstructors tuf =
191-
declUpdates ++ termUpdates
189+
(forall void. Output -> Transaction void) ->
190+
(Name -> Either Output [Name]) ->
191+
TypecheckedUnisonFile Symbol Ann ->
192+
Transaction [(Path, Branch0 m -> Branch0 m)]
193+
typecheckedUnisonFileToBranchUpdates abort getConstructors tuf = do
194+
declUpdates <- makeDeclUpdates abort
195+
pure $ declUpdates ++ termUpdates
192196
where
193-
declUpdates :: [(Path, Branch0 m -> Branch0 m)]
194-
declUpdates =
195-
fold
196-
[ foldMap makeDataDeclUpdates (Map.toList $ UF.dataDeclarationsId' tuf),
197-
foldMap makeEffectDeclUpdates (Map.toList $ UF.effectDeclarationsId' tuf)
198-
]
197+
makeDeclUpdates :: forall m. (forall void. Output -> Transaction void) -> Transaction [(Path, Branch0 m -> Branch0 m)]
198+
makeDeclUpdates abort = do
199+
dataDeclUpdates <- Monoid.foldMapM makeDataDeclUpdates (Map.toList $ UF.dataDeclarationsId' tuf)
200+
effectDeclUpdates <- Monoid.foldMapM makeEffectDeclUpdates (Map.toList $ UF.effectDeclarationsId' tuf)
201+
pure $ dataDeclUpdates <> effectDeclUpdates
199202
where
200203
makeDataDeclUpdates (symbol, (typeRefId, dataDecl)) = makeDeclUpdates (symbol, (typeRefId, Right dataDecl))
201204
makeEffectDeclUpdates (symbol, (typeRefId, effectDecl)) = makeDeclUpdates (symbol, (typeRefId, Left effectDecl))
202-
makeDeclUpdates (symbol, (typeRefId, decl)) =
205+
makeDeclUpdates :: (Symbol, (TypeReferenceId, Decl Symbol Ann)) -> Transaction [(Path, Branch0 m -> Branch0 m)]
206+
makeDeclUpdates (symbol, (typeRefId, decl)) = do
207+
-- some decls will be deleted, we want to delete their
208+
-- constructors as well
209+
deleteConstructorActions <- case map (BranchUtil.makeAnnihilateTermName . Path.splitFromName) <$> getConstructors (Name.unsafeFromVar symbol) of
210+
Left err -> abort err
211+
Right actions -> pure actions
203212
let deleteTypeAction = BranchUtil.makeAnnihilateTypeName split
204-
-- some decls will be deleted, we want to delete their constructors as well
205-
deleteConstructorActions =
206-
map
207-
(BranchUtil.makeAnnihilateTermName . Path.splitFromName)
208-
(getConstructors (Name.unsafeFromVar symbol))
209213
split = splitVar symbol
210214
insertTypeAction = BranchUtil.makeAddTypeName split (Reference.fromId typeRefId) Map.empty
211215
insertTypeConstructorActions =
@@ -218,7 +222,7 @@ typecheckedUnisonFileToBranchUpdates getConstructors tuf =
218222
referentIdsWithNames
219223
deleteStuff = deleteTypeAction : deleteConstructorActions
220224
addStuff = insertTypeAction : insertTypeConstructorActions
221-
in deleteStuff ++ addStuff
225+
pure $ deleteStuff ++ addStuff
222226

223227
termUpdates :: [(Path, Branch0 m -> Branch0 m)]
224228
termUpdates =
@@ -243,36 +247,38 @@ getExistingReferencesNamed defns names = fromTerms <> fromTypes
243247
fromTypes = foldMap (\n -> Relation.lookupDom n $ Names.types names) (defns ^. #types)
244248

245249
buildBigUnisonFile ::
250+
(forall a. Output -> Transaction a) ->
246251
Codebase IO Symbol Ann ->
247252
TypecheckedUnisonFile Symbol Ann ->
248253
Map Reference.Id ReferenceType ->
249254
Names ->
250255
Map ForwardName (Referent, Name) ->
251256
Transaction (UnisonFile Symbol Ann)
252-
buildBigUnisonFile c tuf dependents names ctorNames =
253-
addDefinitionsToUnisonFile c names ctorNames dependents (UF.discardTypes tuf)
257+
buildBigUnisonFile abort c tuf dependents names ctorNames =
258+
addDefinitionsToUnisonFile abort c names ctorNames dependents (UF.discardTypes tuf)
254259

255-
-- | @addDefinitionsToUnisonFile codebase names ctorNames definitions file@ adds all @definitions@ to @file@, avoiding
260+
-- | @addDefinitionsToUnisonFile abort codebase names ctorNames definitions file@ adds all @definitions@ to @file@, avoiding
256261
-- overwriting anything already in @file@. Every definition is put into the file with every naming it has in @names@ "on
257262
-- the left-hand-side of the equals" (but yes type decls don't really have a LHS).
258263
--
259264
-- TODO: find a better module for this function, as it's used in a couple places
260265
addDefinitionsToUnisonFile ::
266+
(forall void. Output -> Transaction void) ->
261267
Codebase IO Symbol Ann ->
262268
Names ->
263269
Map ForwardName (Referent, Name) ->
264270
Map Reference.Id ReferenceType ->
265271
UnisonFile Symbol Ann ->
266272
Transaction (UnisonFile Symbol Ann)
267-
addDefinitionsToUnisonFile c names ctorNames dependents initialUnisonFile =
273+
addDefinitionsToUnisonFile abort c names ctorNames dependents initialUnisonFile =
268274
-- for each dependent, add its definition with all its names to the UnisonFile
269275
foldM addComponent initialUnisonFile (Map.toList dependents')
270276
where
271277
dependents' :: Map Hash ReferenceType = Map.mapKeys (\(Reference.Id h _pos) -> h) dependents
272278
addComponent :: UnisonFile Symbol Ann -> (Hash, ReferenceType) -> Transaction (UnisonFile Symbol Ann)
273279
addComponent uf (h, rt) = case rt of
274280
Reference.RtTerm -> addTermComponent h uf
275-
Reference.RtType -> addDeclComponent h uf
281+
Reference.RtType -> addDeclComponent abort h uf
276282
addTermComponent :: Hash -> UnisonFile Symbol Ann -> Transaction (UnisonFile Symbol Ann)
277283
addTermComponent h uf = do
278284
termComponent <- Codebase.unsafeGetTermComponent c h
@@ -292,32 +298,37 @@ addDefinitionsToUnisonFile c names ctorNames dependents initialUnisonFile =
292298

293299
-- given a dependent hash, include that component in the scratch file
294300
-- todo: wundefined: cut off constructor name prefixes
295-
addDeclComponent :: Hash -> UnisonFile Symbol Ann -> Transaction (UnisonFile Symbol Ann)
296-
addDeclComponent h uf = do
301+
addDeclComponent :: (forall a. Output -> Transaction a) -> Hash -> UnisonFile Symbol Ann -> Transaction (UnisonFile Symbol Ann)
302+
addDeclComponent abort h uf = do
297303
declComponent <- fromJust <$> Codebase.getDeclComponent h
298-
pure $ foldl' addDeclElement uf (zip declComponent [0 ..])
304+
foldM addDeclElement uf (zip declComponent [0 ..])
299305
where
300306
-- for each name a decl has, update its constructor names according to what exists in the namespace
301-
addDeclElement :: UnisonFile Symbol Ann -> (Decl Symbol Ann, Reference.Pos) -> UnisonFile Symbol Ann
307+
addDeclElement :: UnisonFile Symbol Ann -> (Decl Symbol Ann, Reference.Pos) -> Transaction (UnisonFile Symbol Ann)
302308
addDeclElement uf (decl, i) = do
303309
let declNames = Relation.lookupRan (Reference.Derived h i) names.types
304310
-- look up names for this decl's constructor based on the decl's name, and embed them in the decl definition.
305-
foldl' (addRebuiltDefinition decl) uf declNames
311+
foldM (addRebuiltDefinition decl) uf declNames
306312
where
307313
-- skip any definitions that already have names, we don't want to overwrite what the user has supplied
314+
addRebuiltDefinition :: (Decl Symbol Ann) -> UnisonFile Symbol Ann -> Name -> Transaction (UnisonFile Symbol Ann)
308315
addRebuiltDefinition decl uf name = case decl of
309-
Left ed -> uf {UF.effectDeclarationsId = Map.insertWith (\_new old -> old) (Name.toVar name) (Reference.Id h i, Decl.EffectDeclaration $ overwriteConstructorNames name ed.toDataDecl) uf.effectDeclarationsId}
310-
Right dd -> uf {UF.dataDeclarationsId = Map.insertWith (\_new old -> old) (Name.toVar name) (Reference.Id h i, overwriteConstructorNames name dd) uf.dataDeclarationsId}
311-
overwriteConstructorNames :: Name -> DataDeclaration Symbol Ann -> DataDeclaration Symbol Ann
316+
Left ed ->
317+
overwriteConstructorNames name ed.toDataDecl >>= \case
318+
ed' -> pure uf {UF.effectDeclarationsId = Map.insertWith (\_new old -> old) (Name.toVar name) (Reference.Id h i, Decl.EffectDeclaration ed') uf.effectDeclarationsId}
319+
Right dd ->
320+
overwriteConstructorNames name dd >>= \case
321+
dd' -> pure uf {UF.dataDeclarationsId = Map.insertWith (\_new old -> old) (Name.toVar name) (Reference.Id h i, dd') uf.dataDeclarationsId}
322+
overwriteConstructorNames :: Name -> DataDeclaration Symbol Ann -> Transaction (DataDeclaration Symbol Ann)
312323
overwriteConstructorNames name dd =
313-
let constructorNames :: [Symbol]
314-
constructorNames =
315-
Name.toVar . fromJust . Name.stripNamePrefix name
316-
<$> findCtorNames names ctorNames (Just $ Decl.constructorCount dd) name
324+
let constructorNames :: Transaction [Symbol]
325+
constructorNames = case findCtorNames names ctorNames (Just $ Decl.constructorCount dd) name of
326+
Left err -> abort err
327+
Right array -> pure $ Name.toVar . fromJust . Name.stripNamePrefix name <$> array
317328
swapConstructorNames oldCtors =
318329
let (annotations, _vars, types) = unzip3 oldCtors
319-
in zip3 annotations constructorNames types
320-
in over Decl.constructors_ swapConstructorNames dd
330+
in zip3 annotations <$> constructorNames <*> pure types
331+
in Lens.traverseOf Decl.constructors_ swapConstructorNames dd
321332

322333
-- | O(r + c * d) touches all the referents (r), and all the NameSegments (d) of all of the Con referents (c)
323334
forwardCtorNames :: Names -> Map ForwardName (Referent, Name)
@@ -329,7 +340,7 @@ forwardCtorNames names =
329340
]
330341

331342
-- | given a decl name, find names for all of its constructors, in order.
332-
findCtorNames :: Names -> Map ForwardName (Referent, Name) -> Maybe Int -> Name -> [Name]
343+
findCtorNames :: Names -> Map ForwardName (Referent, Name) -> Maybe Int -> Name -> Either Output.Output [Name]
333344
findCtorNames names forwardCtorNames ctorCount n =
334345
let declRef = Set.findMin $ Relation.lookupDom n names.types
335346
f = ForwardName.fromName n
@@ -348,9 +359,10 @@ findCtorNames names forwardCtorNames ctorCount n =
348359
m = foldl' insertShortest mempty (Foldable.toList center)
349360
ctorCountGuess = fromMaybe (Map.size m) ctorCount
350361
in if Map.size m == ctorCountGuess && all (isJust . flip Map.lookup m) [0 .. fromIntegral ctorCountGuess - 1]
351-
then Map.elems m
352-
else error $ "incomplete constructor mapping for " ++ show n ++ ": " ++ show (Map.keys m) ++ " out of " ++ show ctorCountGuess
362+
then Right $ Map.elems m
363+
else Left $ Output.UpdateIncompleteConstructorSet n m ctorCountGuess
353364

365+
-- Used by `findCtorNames` to filter `forwardCtorNames` to a narrow range which will be searched linearly.
354366
-- >>> incrementLastSegmentChar $ ForwardName.fromName $ Name.unsafeFromText "foo.bar.quux"
355367
-- ForwardName {toList = "foo" :| ["bar","quuy"]}
356368
incrementLastSegmentChar :: ForwardName -> ForwardName
@@ -367,7 +379,7 @@ incrementLastSegmentChar (ForwardName segments) =
367379
else Text.init text `Text.append` Text.singleton (succ $ Text.last text)
368380
in NameSegment incrementedText
369381

370-
getTermAndDeclNames :: Var v => TypecheckedUnisonFile v a -> Defns (Set Name) (Set Name)
382+
getTermAndDeclNames :: (Var v) => TypecheckedUnisonFile v a -> Defns (Set Name) (Set Name)
371383
getTermAndDeclNames tuf = Defns (terms <> effectCtors <> dataCtors) (effects <> datas)
372384
where
373385
terms = keysToNames $ UF.hashTermsId tuf

unison-cli/src/Unison/Codebase/Editor/HandleInput/Upgrade.hs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,15 @@ handleUpgrade oldDepName newDepName = do
9898
-- mything#mything2 = #newfoo + 10
9999

100100
(unisonFile, printPPE) <-
101-
Cli.runTransaction do
101+
Cli.runTransactionWithRollback \abort -> do
102102
-- Create a Unison file that contains all of our dependents of things in `lib.old`.
103103
unisonFile <- do
104104
dependents <-
105105
Operations.dependentsWithinScope
106106
(Names.referenceIds namesExcludingLibdeps)
107107
(Branch.deepTermReferences oldDepV1Branch <> Branch.deepTypeReferences oldDepV1Branch)
108108
addDefinitionsToUnisonFile
109+
abort
109110
codebase
110111
namesExcludingLibdeps
111112
constructorNamesExcludingLibdeps
@@ -154,16 +155,16 @@ handleUpgrade oldDepName newDepName = do
154155
Cli.respond (Output.UpgradeFailure oldDepName newDepName)
155156
Cli.returnEarlyWithoutOutput
156157

157-
Cli.runTransaction (Codebase.addDefsToCodebase codebase typecheckedUnisonFile)
158+
branchUpdates <- Cli.runTransactionWithRollback \abort -> do
159+
Codebase.addDefsToCodebase codebase typecheckedUnisonFile
160+
typecheckedUnisonFileToBranchUpdates
161+
abort
162+
(findCtorNames namesExcludingLibdeps constructorNamesExcludingLibdeps Nothing)
163+
typecheckedUnisonFile
158164
Cli.stepAt
159165
textualDescriptionOfUpgrade
160166
( Path.unabsolute projectPath,
161-
deleteLibdep oldDepName
162-
. Branch.batchUpdates
163-
( typecheckedUnisonFileToBranchUpdates
164-
(findCtorNames namesExcludingLibdeps constructorNamesExcludingLibdeps Nothing)
165-
typecheckedUnisonFile
166-
)
167+
deleteLibdep oldDepName . Branch.batchUpdates branchUpdates
167168
)
168169
Cli.respond (Output.UpgradeSuccess oldDepName newDepName)
169170
where

unison-cli/src/Unison/Codebase/Editor/Output.hs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import Unison.Codebase.ShortCausalHash qualified as SCH
4949
import Unison.Codebase.Type (GitError)
5050
import Unison.CommandLine.InputPattern qualified as Input
5151
import Unison.DataDeclaration (Decl)
52+
import Unison.DataDeclaration.ConstructorId (ConstructorId)
5253
import Unison.HashQualified qualified as HQ
5354
import Unison.HashQualified' qualified as HQ'
5455
import Unison.LabeledDependency (LabeledDependency)
@@ -389,6 +390,7 @@ data Output
389390
| UpdateStartTypechecking
390391
| UpdateTypecheckingFailure
391392
| UpdateTypecheckingSuccess
393+
| UpdateIncompleteConstructorSet Name (Map ConstructorId Name) Int
392394
| UpgradeFailure !NameSegment !NameSegment
393395
| UpgradeSuccess !NameSegment !NameSegment
394396

@@ -456,6 +458,7 @@ isFailure o = case o of
456458
UpdateStartTypechecking -> False
457459
UpdateTypecheckingFailure {} -> True
458460
UpdateTypecheckingSuccess {} -> False
461+
UpdateIncompleteConstructorSet {} -> True
459462
AmbiguousCloneLocal {} -> True
460463
AmbiguousCloneRemote {} -> True
461464
ClonedProjectBranch {} -> False

unison-cli/src/Unison/CommandLine/OutputMessages.hs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2186,6 +2186,25 @@ notifyUser dir = \case
21862186
<> "Once the file is compiling, try"
21872187
<> makeExample' IP.update
21882188
<> "again."
2189+
UpdateIncompleteConstructorSet name ctorMap expectedCount ->
2190+
pure $
2191+
P.lines
2192+
[ P.wrap $
2193+
"I couldn't complete the update because I couldn't find"
2194+
<> fromString (show expectedCount)
2195+
<> "constructor(s) for"
2196+
<> prettyName name
2197+
<> "where I expected to."
2198+
<> "I found:"
2199+
<> fromString (show (Map.toList ctorMap)),
2200+
"",
2201+
P.wrap $
2202+
"You can use"
2203+
<> P.indentNAfterNewline 2 (IP.makeExample IP.view [prettyName name])
2204+
<> "and"
2205+
<> P.indentNAfterNewline 2 (IP.makeExample IP.aliasTerm ["<hash>", prettyName name <> ".<ConstructorName>"])
2206+
<> "to give names to each constructor, and then try again."
2207+
]
21892208
UpgradeFailure old new ->
21902209
pure . P.wrap $
21912210
"I couldn't automatically upgrade"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
```ucm:hide
2+
.> builtins.merge
3+
.> move.namespace builtin lib.builtin
4+
```
5+
6+
```unison
7+
unique type Foo = Bar Nat
8+
```
9+
10+
```ucm
11+
.> add
12+
.> delete.term Foo.Bar
13+
```
14+
15+
Now we've set up a situation where the original constructor missing.
16+
17+
```unison
18+
unique type Foo = Bar Nat Nat
19+
```
20+
21+
```ucm:error
22+
.> view Foo
23+
.> update
24+
```

0 commit comments

Comments
 (0)