@@ -5,46 +5,18 @@ module Language.PureScript.Sugar.ObjectWildcards
6import Prelude.Compat
6import Prelude.Compat
8−import Control.Monad (forM, foldM)
8+import Control.Monad (forM)
9import Control.Monad.Error.Class (MonadError(..))
9import Control.Monad.Error.Class (MonadError(..))
10import Control.Monad.Supply.Class
10import Control.Monad.Supply.Class
11−import Data.List (partition)
11+import Data.Foldable (toList)
12−import Data.List.NonEmpty (NonEmpty(..))
12+import Data.List (foldl')
14−import qualified Data.Map as M
15import Data.Maybe (catMaybes)
13import Data.Maybe (catMaybes)
16import Language.PureScript.AST
14import Language.PureScript.AST
17import Language.PureScript.Environment (NameKind(..))
15import Language.PureScript.Environment (NameKind(..))
18import Language.PureScript.Errors
16import Language.PureScript.Errors
19−import Language.PureScript.Label (Label(..))
20import Language.PureScript.Names
17import Language.PureScript.Names
21import Language.PureScript.PSString (PSString)
18import Language.PureScript.PSString (PSString)
23−-- `PathNode` and `PathTree` are used as an intermediate form when desugaring a nested object update.
24−-- For an update such as:
30−-- We represent the updates as the `PathTree`:
32−-- M.fromList [ ("foo", Leaf 3)
33−-- , ("bar", Branch (M.fromList [ ("baz", Leaf 1)
34−-- , ("qux", Leaf 2) ]) ])
36−-- Which we then convert to an expression representing the following:
40−-- , bar = x'.bar { baz = 1
43−-- The `let` here is required to prevent re-evaluating the object expression `x`.
44−-- However we don't generate this when using an anonymous argument for the object.
46−type PathTree = Map PSString PathNode
47−data PathNode = Leaf Expr | Branch PathTree
49desugarObjectConstructors
21desugarObjectConstructors
50 :: forall m
22 :: forall m
@@ -70,11 +42,7 @@ desugarDecl other = fn other
70 , BinaryNoParens op u val <- b'
42 , BinaryNoParens op u val <- b'
71 , isAnonymousArgument u = do arg <- freshIdent'
43 , isAnonymousArgument u = do arg <- freshIdent'
72 return $ Abs (Left arg) $ App (App op (Var (Qualified Nothing arg))) val
44 return $ Abs (Left arg) $ App (App op (Var (Qualified Nothing arg))) val
73− desugarExpr (Literal (ObjectLiteral ps)) = wrapLambda (Literal . ObjectLiteral) ps
45+ desugarExpr (Literal (ObjectLiteral ps)) = wrapLambdaAssoc (Literal . ObjectLiteral) ps
74− desugarExpr (ObjectUpdate u ps) | isAnonymousArgument u = do
76− Abs (Left obj) <$> wrapLambda (ObjectUpdate (argToExpr obj)) ps
77− desugarExpr (ObjectUpdate obj ps) = wrapLambda (ObjectUpdate obj) ps
78 desugarExpr (ObjectUpdateNested obj ps) = transformNestedUpdate obj ps
46 desugarExpr (ObjectUpdateNested obj ps) = transformNestedUpdate obj ps
79 desugarExpr (Accessor prop u)
47 desugarExpr (Accessor prop u)
80 | Just props <- peelAnonAccessorChain u = do
48 | Just props <- peelAnonAccessorChain u = do
@@ -92,67 +60,42 @@ desugarDecl other = fn other
92 return $ foldr (Abs . Left) if_ (catMaybes [u', t', f'])
60 return $ foldr (Abs . Left) if_ (catMaybes [u', t', f'])
93 desugarExpr e = return e
61 desugarExpr e = return e
95− transformNestedUpdate :: Expr -> [(NonEmpty PSString, Expr)] -> m Expr
63+ transformNestedUpdate :: Expr -> PathTree Expr -> m Expr
96 transformNestedUpdate obj ps = do
64 transformNestedUpdate obj ps = do
97 -- If we don't have an anonymous argument then we need to generate a let wrapper
65 -- If we don't have an anonymous argument then we need to generate a let wrapper
98 -- so that the object expression isn't re-evaluated for each nested update.
66 -- so that the object expression isn't re-evaluated for each nested update.
99 val <- freshIdent'
67 val <- freshIdent'
68+ let valExpr = argToExpr val
100 if isAnonymousArgument obj
69 if isAnonymousArgument obj
101− then Abs (Left val) <$> wrapLambdaM (build val) ps
70+ then Abs (Left val) <$> wrapLambda (buildUpdates valExpr) ps
102− else wrapLambdaM (fmap (buildLet val) . build val) ps
71+ else wrapLambda (buildLet val . buildUpdates valExpr) ps
104− build val xs = buildUpdates (argToExpr val) <$> foldM buildTree M.empty xs
105 buildLet val = Let [ValueDeclaration val Public [] (Right obj)]
73 buildLet val = Let [ValueDeclaration val Public [] (Right obj)]
107− -- Here we build up the intermediate `PathTree` data structure and check that
108− -- the paths are valid relative to each other - for example, the update
109− -- `{ foo.bar = 2, foo = {bar: 3} }` is invalid because there are conflicting
113− -> (NonEmpty PSString, Expr)
115− buildTree pathTree (path, e) = go pathTree path where
117− -- path already exists
118− | key `M.member` tree = throwError . errorMessage $ DuplicateLabel (Label key) (Just (ObjectUpdateNested obj ps))
120− | otherwise = return (M.insert key (Leaf e) tree)
121− go tree (key :| (x : xs)) = do
122− branch <- case M.lookup key tree of
123− -- nothing at this path yet
124− Nothing -> return M.empty
125− -- already a map at this path
126− Just (Branch branch) -> return branch
127− -- sub-path already exists
128− Just (Leaf _) -> throwError . errorMessage $ DuplicateLabel (Label key) (Just (ObjectUpdateNested obj ps))
129− M.insert key . Branch <$> go branch (x :| xs) <*> pure tree
131− -- Now we have a valid collection of updates in the form of a `PathTree`
132− -- we can recursively build up the nested `ObjectUpdate` expressions.
75+ -- recursively build up the nested `ObjectUpdate` expressions
133− buildUpdates :: Expr -> PathTree -> Expr
76+ buildUpdates :: Expr -> PathTree Expr -> Expr
134− buildUpdates val vs = ObjectUpdate val (goLayer [] <$> M.toList vs) where
77+ buildUpdates val (PathTree vs) = ObjectUpdate val (goLayer [] <$> runAssocList vs) where
135− goLayer :: [PSString] -> (PSString, PathNode) -> (PSString, Expr)
78+ goLayer :: [PSString] -> (PSString, PathNode Expr) -> (PSString, Expr)
136 goLayer _ (key, Leaf expr) = (key, expr)
79 goLayer _ (key, Leaf expr) = (key, expr)
137− goLayer path (key, Branch branch) =
80+ goLayer path (key, Branch (PathTree branch)) =
138 let path' = path ++ [key]
81 let path' = path ++ [key]
139− updates = goLayer path' <$> M.toList branch
82+ updates = goLayer path' <$> runAssocList branch
140− accessor = foldr Accessor val path'
83+ accessor = foldl' (flip Accessor) val path'
141 objectUpdate = ObjectUpdate accessor updates
84 objectUpdate = ObjectUpdate accessor updates
142 in (key, objectUpdate)
85 in (key, objectUpdate)
144− wrapLambdaM :: forall k. ([(k, Expr)] -> m Expr) -> [(k, Expr)] -> m Expr
87+ wrapLambda :: forall t. Traversable t => (t Expr -> Expr) -> t Expr -> m Expr
145− wrapLambdaM mkVal ps =
88+ wrapLambda mkVal ps = do
146− let (args, props) = partition (isAnonymousArgument . snd) ps
89+ args <- traverse processExpr ps
150− (args', ps') <- unzip <$> mapM mkProp ps
152− return $ foldr (Abs . Left) val (catMaybes args')
90+ return $ foldr (Abs . Left) (mkVal (snd <$> args)) (catMaybes $ toList (fst <$> args))
92+ processExpr :: Expr -> m (Maybe Ident, Expr)
95+ return (arg, maybe e argToExpr arg)
154− wrapLambda :: forall k. ([(k, Expr)] -> Expr) -> [(k, Expr)] -> m Expr
97+ wrapLambdaAssoc :: ([(PSString, Expr)] -> Expr) -> [(PSString, Expr)] -> m Expr
155− wrapLambda mkVal = wrapLambdaM (return . mkVal)
98+ wrapLambdaAssoc mkVal = wrapLambda (mkVal . runAssocList) . AssocList
157 stripPositionInfo :: Expr -> Expr
100 stripPositionInfo :: Expr -> Expr
158 stripPositionInfo (PositionedValue _ _ e) = stripPositionInfo e
101 stripPositionInfo (PositionedValue _ _ e) = stripPositionInfo e
@@ -169,11 +112,6 @@ desugarDecl other = fn other
169 isAnonymousArgument (PositionedValue _ _ e) = isAnonymousArgument e
112 isAnonymousArgument (PositionedValue _ _ e) = isAnonymousArgument e
170 isAnonymousArgument _ = False
113 isAnonymousArgument _ = False
172− mkProp :: forall k. (k, Expr) -> m (Maybe Ident, (k, Expr))
173− mkProp (name, e) = do
174− arg <- freshIfAnon e
175− return (arg, (name, maybe e argToExpr arg))
177 freshIfAnon :: Expr -> m (Maybe Ident)
115 freshIfAnon :: Expr -> m (Maybe Ident)
178 freshIfAnon u
116 freshIfAnon u
179 | isAnonymousArgument u = Just <$> freshIdent'
117 | isAnonymousArgument u = Just <$> freshIdent'