Skip to content

Commit

Permalink
feat: Allow typedef deletions regardless of whether type is in use
Browse files Browse the repository at this point in the history
This includes fixing up expressions utilising the deleted type/constructor/field so that they still make sense. Mostly this involves replacing subexpressions with holes.

Note that this does not yet include deleting type parameters, which we still cannot modify at all when the type is in use. But we aim to relax this restriction as well shortly.

We add a unit tests for each action. Note that we didn't actually have any for the old behaviour, due to time constraints when that was implemented. Although the `tasty_available_actions_accepted` property test caught a lot of issues for us.

Signed-off-by: George Thomas <georgefsthomas@gmail.com>
  • Loading branch information
georgefst committed Nov 28, 2023
1 parent 775651d commit 26aaa83
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 58 deletions.
26 changes: 12 additions & 14 deletions primer/src/Primer/Action/Available.hs
Original file line number Diff line number Diff line change
Expand Up @@ -377,14 +377,11 @@ forTypeDef l Editable tydefs defs tdName td =
sortByPriority l
$ [ Input RenameType
, Input AddCon
, NoInput DeleteTypeDef
]
<> mwhen
(not $ typeInUse tdName td tydefs defs)
( [NoInput DeleteTypeDef]
<> mwhen
(l == Expert)
[Input AddTypeParam]
)
(l == Expert && not (typeInUse tdName td tydefs defs))
[Input AddTypeParam]

forTypeDefParamNode ::
TyVarName ->
Expand Down Expand Up @@ -443,12 +440,13 @@ forTypeDefConsNode ::
ASTTypeDef TypeMeta KindMeta ->
[Action]
forTypeDefConsNode _ NonEditable _ _ _ _ = mempty
forTypeDefConsNode l Editable tydefs defs tdName td =
sortByPriority l
$ [ NoInput AddConField
, Input RenameCon
]
<> mwhen (not $ typeInUse tdName td tydefs defs) [NoInput DeleteCon]
forTypeDefConsNode l Editable _ _ _ _ =
sortByPriority
l
[ NoInput AddConField
, Input RenameCon
, NoInput DeleteCon
]

forTypeDefConsFieldNode ::
ValConName ->
Expand All @@ -462,9 +460,9 @@ forTypeDefConsFieldNode ::
ASTTypeDef TypeMeta KindMeta ->
[Action]
forTypeDefConsFieldNode _ _ _ _ NonEditable _ _ _ _ = mempty
forTypeDefConsFieldNode con index id l Editable tydefs defs tdName td =
forTypeDefConsFieldNode con index id l Editable _ _ _ td =
sortByPriority l
$ mwhen ((view _id <$> fieldType) == Just id && not (typeInUse tdName td tydefs defs)) [NoInput DeleteConField]
$ mwhen ((view _id <$> fieldType) == Just id) [NoInput DeleteConField]
<> case findTypeOrKind id =<< fieldType of
Nothing -> mempty
Just (Left t) -> forType l t
Expand Down
151 changes: 108 additions & 43 deletions primer/src/Primer/App.hs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ import Primer.Core (
CaseBranch,
CaseBranch' (CaseBranch),
CaseFallback,
CaseFallback' (CaseExhaustive),
Expr,
Expr' (Case, Con, EmptyHole, Var),
GVarName,
Expand Down Expand Up @@ -176,7 +177,7 @@ import Primer.Core (
_type,
_typeMetaLens,
)
import Primer.Core.DSL (S, create)
import Primer.Core.DSL (S, create, emptyHole, tEmptyHole)
import Primer.Core.DSL qualified as DSL
import Primer.Core.Transform (renameTyVar, renameVar, unfoldTApp)
import Primer.Core.Utils (
Expand Down Expand Up @@ -633,7 +634,7 @@ handleEvalFullRequest (EvalFullReq{evalFullReqExpr, evalFullCxtDir, evalFullMaxS
Right nf -> EvalFullRespNormal nf

-- | Handle a 'ProgAction'
applyProgAction :: MonadEdit m ProgError => Prog -> ProgAction -> m Prog
applyProgAction :: forall m. MonadEdit m ProgError => Prog -> ProgAction -> m Prog
applyProgAction prog = \case
MoveToDef d -> do
m <- lookupEditableModule (qualifiedModule d) prog
Expand Down Expand Up @@ -698,14 +699,45 @@ applyProgAction prog = \case
( m{moduleTypes = tydefs'}
, Just $ SelectionTypeDef $ TypeDefSelection tc Nothing
)
DeleteTypeDef d -> editModuleCross (qualifiedModule d) prog $ \(m, ms) ->
case moduleTypesQualified m Map.!? d of
Nothing -> throwError $ TypeDefNotFound d
Just (TypeDefPrim _) -> throwError $ TypeDefIsPrim d
Just (TypeDefAST td) -> do
checkTypeNotInUse d td $ m : ms
let m' = m{moduleTypes = Map.delete (baseName d) (moduleTypes m)}
pure (m' : ms, Nothing)
DeleteTypeDef d -> editModuleOfCrossType (Just d) prog $ \(m, ms) defName def -> do
let updateExpr = \case
Con _ c _ | c `elem` map valConName (astTypeDefConstructors def) -> emptyHole
e -> pure e
ms' <-
((m & over #moduleTypes (Map.delete defName)) : ms)
& ( traverseOf
(traversed % #moduleDefs % traversed % #_DefAST)
( traverseOf
#astDefExpr
( transformM (traverseOf typesInExpr updateType)
<=< transformM updateExpr
)
<=< traverseOf #astDefType updateType
)
<=< traverseOf
( traversed
% #moduleTypes
% traversed
% #_TypeDefAST
% #astTypeDefConstructors
% traversed
% #valConArgs
% traversed
)
updateType
)
-- There would otherwise be some awkward cases to patch up, such as modifying metadata in type annotations
-- to reflect the change from `KType` to `KHole`.
-- Note that this also automatically cleans up all pattern matches on this type,
-- since we will have set the type of the scrutinee to a hole,
-- and thus the typechecker removes all branches.
-- This is why we don't bother explicitly fixing up patterns in the above traversals.
ms'' <- runFullTCPass (progSmartHoles prog) (progImports prog) ms'
pure (ms'', Nothing)
where
updateType = transformM \case
TCon _ n | n == d -> tEmptyHole
e -> pure e
RenameType old (unsafeMkName -> nameRaw) -> editModuleCross (qualifiedModule old) prog $ \(m, ms) -> do
when (new /= old && new `elem` allTyConNames prog) $ throwError $ TypeDefAlreadyExists new
m' <- traverseOf #moduleTypes updateTypeDef m
Expand Down Expand Up @@ -830,24 +862,41 @@ applyProgAction prog = \case
td
)
type_
DeleteCon tdName vcName -> editModuleCross (qualifiedModule tdName) prog $ \(m, ms) -> do
m' <-
alterTypeDef
( \td -> do
checkTypeNotInUse tdName td $ m : ms
traverseOf
#astTypeDefConstructors
( \cons -> do
unless
(vcName `elem` map valConName cons)
(throwError $ ConNotFound vcName)
pure $ filter ((/= vcName) . valConName) cons
)
td
DeleteCon tdName vcName -> editModuleOfCrossType (Just tdName) prog $ \(m, ms) defName def -> do
def' <-
traverseOf
#astTypeDefConstructors
( \cons -> do
unless
(vcName `elem` map valConName cons)
(throwError $ ConNotFound vcName)
pure $ filter ((/= vcName) . valConName) cons
)
tdName
m
pure (m' : ms, Just $ SelectionTypeDef $ TypeDefSelection tdName Nothing)
def
ms' <-
((m & over #moduleTypes (Map.insert defName $ TypeDefAST def')) : ms)
& traverseOf
(traversed % #moduleDefs % traversed % #_DefAST % #astDefExpr)
( transformCaseBranches
tdName
( \_ (bs, fb) ->
let bs' =
bs
& filter \case
CaseBranch (PatCon vcName') _ _ | vcName' == vcName -> False
_ -> True
in pure
( bs'
, if map (PatCon . valConName) (astTypeDefConstructors def') == map caseBranchName bs'
then CaseExhaustive
else fb
)
)
<=< transformM \case
Con _ c _ | c == vcName -> emptyHole
e -> pure e
)
pure (ms', Just $ SelectionTypeDef $ TypeDefSelection tdName Nothing)
AddConField type_ con index new ->
editModuleCross (qualifiedModule type_) prog $ \(m, ms) -> do
m' <- updateTypeDef m
Expand Down Expand Up @@ -900,31 +949,47 @@ applyProgAction prog = \case
newName <- LocalName <$> freshName (freeVars e)
binds' <- maybe (throwError $ IndexOutOfRange index) pure $ insertAt index (Bind m' newName) binds
pure $ CaseBranch vc binds' e
DeleteConField tdName vcName index -> editModuleCross (qualifiedModule tdName) prog $ \(m, ms) -> do
m' <-
alterTypeDef
( \td -> do
checkTypeNotInUse tdName td $ m : ms
traverseOf
#astTypeDefConstructors
( maybe (throwError $ ConNotFound vcName) pure
<=< findAndAdjustA
((== vcName) . valConName)
(traverseOf #valConArgs $ maybe (throwError $ IndexOutOfRange index) pure . map fst . deleteAt index)
)
td
DeleteConField tdName vcName index -> editModuleOfCrossType (Just tdName) prog $ \(m, ms) defName def -> do
def' <-
traverseOf
#astTypeDefConstructors
( maybe (throwError $ ConNotFound vcName) pure
<=< findAndAdjustA
((== vcName) . valConName)
(traverseOf #valConArgs $ map fst . deleteIndex)
)
tdName
m
def
ms' <-
((m & over #moduleTypes (Map.insert defName $ TypeDefAST def')) : ms)
& traverseOf
(traversed % #moduleDefs % traversed % #_DefAST % #astDefExpr)
( transformNamedCaseBranch
tdName
vcName
( \_ (CaseBranch vc binds e) -> do
(binds', Bind _ var) <- deleteIndex binds
CaseBranch vc binds'
<$> ( e & traverseOf _freeTmVars \case
(_, v) | v == var -> emptyHole
(meta, v) -> pure $ Var meta $ LocalVarRef v
)
)
<=< transformM \case
Con meta c es | c == vcName -> map (Con meta c . fst) $ deleteIndex es
e -> pure e
)
pure
( m' : ms
( ms'
, Just
. SelectionTypeDef
. TypeDefSelection tdName
. Just
. TypeDefConsNodeSelection
$ TypeDefConsSelection vcName Nothing
)
where
deleteIndex :: [a] -> m ([a], a)
deleteIndex = maybe (throwError $ IndexOutOfRange index) pure . deleteAt index
AddTypeParam tdName index paramName0 k -> editModuleCross (qualifiedModule tdName) prog $ \(m, ms) -> do
let paramName = unsafeMkLocalName paramName0
m' <-
Expand Down
5 changes: 4 additions & 1 deletion primer/test/Tests/Action/Available.hs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ import Primer.App (
)
import Primer.App qualified as App
import Primer.App.Base (TypeDefConsFieldSelection (..))
import Primer.App.Utils (forgetProgTypecache)
import Primer.Builtins (builtinModuleName, cCons, cFalse, cTrue, tBool, tList, tNat)
import Primer.Core (
Expr,
Expand Down Expand Up @@ -533,7 +534,9 @@ data SucceedOrCapture a
ensureSHNormal :: App -> PropertyT WT ()
ensureSHNormal a = case checkAppWellFormed a of
Left err -> annotateShow err >> failure
Right a' -> TypeCacheAlpha a === TypeCacheAlpha a'
Right a' ->
TypeCacheAlpha (forgetProgTypecache $ appProg a)
=== TypeCacheAlpha (forgetProgTypecache $ appProg a')

genLoc ::
forall m.
Expand Down
Loading

0 comments on commit 26aaa83

Please sign in to comment.