Skip to content

Commit 32faa76

Browse files
LiamGoodacrepaf31
authored andcommitted
Change nested record update syntax and fix bug with depth > 2 (purescript#2580)
* Change nested record update syntax and fix bug with depth > 2 * Build update tree in parser * Remove accidental pragmas
1 parent c05106c commit 32faa76

7 files changed

Lines changed: 107 additions & 126 deletions

File tree

examples/passing/NestedRecordUpdate.purs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,22 @@ module Main where
33
import Prelude
44
import Control.Monad.Eff.Console
55

6-
type T = { foo :: Int, bar :: { baz :: Int, qux :: Int } }
6+
type T = { foo :: Int, bar :: { baz :: Int, qux :: { lhs :: Int, rhs :: Int } } }
77

88
init :: T
9-
init = { foo: 1, bar: { baz: 2, qux: 3 } }
9+
init = { foo: 1, bar: { baz: 2, qux: { lhs: 3, rhs: 4 } } }
1010

1111
updated :: T
12-
updated = init { foo = 10, bar.baz = 20, bar.qux = 30 }
12+
updated = init { foo = 10, bar { baz = 20, qux { lhs = 30, rhs = 40 } } }
1313

1414
expected :: T
15-
expected = { foo: 10, bar: { baz: 20, qux: 30 } }
15+
expected = { foo: 10, bar: { baz: 20, qux: { lhs: 30, rhs: 40 } } }
1616

1717
check l r =
1818
l.foo == r.foo &&
1919
l.bar.baz == r.bar.baz &&
20-
l.bar.qux == r.bar.qux
20+
l.bar.qux.lhs == r.bar.qux.lhs &&
21+
l.bar.qux.rhs == r.bar.qux.rhs
2122

2223
main = do
2324
when (check updated expected) $ log "Done"

examples/passing/NestedRecordUpdateWildcards.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module Main where
33
import Prelude
44
import Control.Monad.Eff.Console
55

6-
update = _ { foo = _, bar.baz = _, bar.qux = _ }
6+
update = _ { foo = _, bar { baz = _, qux = _ } }
77

88
init = { foo: 1, bar: { baz: 2, qux: 3 } }
99

src/Language/PureScript/AST/Declarations.hs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
{-# LANGUAGE TemplateHaskell #-}
2+
{-# LANGUAGE DeriveFoldable #-}
3+
{-# LANGUAGE DeriveTraversable #-}
24

35
-- |
46
-- Data types for modules and declarations
@@ -12,7 +14,6 @@ import Control.Monad.Identity
1214
import Data.Aeson.TH
1315
import qualified Data.Map as M
1416
import Data.Text (Text)
15-
import Data.List.NonEmpty (NonEmpty(..))
1617

1718
import Language.PureScript.AST.Binders
1819
import Language.PureScript.AST.Literals
@@ -587,10 +588,10 @@ data Expr
587588
--
588589
| ObjectUpdate Expr [(PSString, Expr)]
589590
-- |
590-
-- Object updates with nested support: `x { foo.bar = e }`
591+
-- Object updates with nested support: `x { foo { bar = e } }`
591592
-- Replaced during desugaring into a `Let` and nested `ObjectUpdate`s
592593
--
593-
| ObjectUpdateNested Expr [(NonEmpty PSString, Expr)]
594+
| ObjectUpdateNested Expr (PathTree Expr)
594595
-- |
595596
-- Function introduction
596597
--
@@ -706,5 +707,38 @@ data DoNotationElement
706707
| PositionedDoNotationElement SourceSpan [Comment] DoNotationElement
707708
deriving (Show)
708709

710+
711+
-- For a record update such as:
712+
--
713+
-- x { foo = 0
714+
-- , bar { baz = 1
715+
-- , qux = 2 } }
716+
--
717+
-- We represent the updates as the `PathTree`:
718+
--
719+
-- [ ("foo", Leaf 3)
720+
-- , ("bar", Branch [ ("baz", Leaf 1)
721+
-- , ("qux", Leaf 2) ]) ]
722+
--
723+
-- Which we then convert to an expression representing the following:
724+
--
725+
-- let x' = x
726+
-- in x' { foo = 0
727+
-- , bar = x'.bar { baz = 1
728+
-- , qux = 2 } }
729+
--
730+
-- The `let` here is required to prevent re-evaluating the object expression `x`.
731+
-- However we don't generate this when using an anonymous argument for the object.
732+
--
733+
734+
newtype PathTree t = PathTree (AssocList PSString (PathNode t))
735+
deriving (Show, Eq, Ord, Functor, Foldable, Traversable)
736+
737+
data PathNode t = Leaf t | Branch (PathTree t)
738+
deriving (Show, Eq, Ord, Functor, Foldable, Traversable)
739+
740+
newtype AssocList k t = AssocList { runAssocList :: [(k, t)] }
741+
deriving (Show, Eq, Ord, Foldable, Functor, Traversable)
742+
709743
$(deriveJSON (defaultOptions { sumEncoding = ObjectWithSingleField }) ''DeclarationRef)
710744
$(deriveJSON (defaultOptions { sumEncoding = ObjectWithSingleField }) ''ImportDeclarationType)

src/Language/PureScript/AST/Traversals.hs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ everywhereOnValues f g h = (f', g', h')
4848
g' (TypeClassDictionaryConstructorApp name v) = g (TypeClassDictionaryConstructorApp name (g' v))
4949
g' (Accessor prop v) = g (Accessor prop (g' v))
5050
g' (ObjectUpdate obj vs) = g (ObjectUpdate (g' obj) (map (fmap g') vs))
51-
g' (ObjectUpdateNested obj vs) = g (ObjectUpdateNested (g' obj) (map (fmap g') vs))
51+
g' (ObjectUpdateNested obj vs) = g (ObjectUpdateNested (g' obj) (fmap g' vs))
5252
g' (Abs name v) = g (Abs name (g' v))
5353
g' (App v1 v2) = g (App (g' v1) (g' v2))
5454
g' (IfThenElse v1 v2 v3) = g (IfThenElse (g' v1) (g' v2) (g' v3))
@@ -116,7 +116,7 @@ everywhereOnValuesTopDownM f g h = (f' <=< f, g' <=< g, h' <=< h)
116116
g' (TypeClassDictionaryConstructorApp name v) = TypeClassDictionaryConstructorApp name <$> (g v >>= g')
117117
g' (Accessor prop v) = Accessor prop <$> (g v >>= g')
118118
g' (ObjectUpdate obj vs) = ObjectUpdate <$> (g obj >>= g') <*> traverse (sndM (g' <=< g)) vs
119-
g' (ObjectUpdateNested obj vs) = ObjectUpdateNested <$> (g obj >>= g') <*> traverse (sndM (g' <=< g)) vs
119+
g' (ObjectUpdateNested obj vs) = ObjectUpdateNested <$> (g obj >>= g') <*> traverse (g' <=< g) vs
120120
g' (Abs name v) = Abs name <$> (g v >>= g')
121121
g' (App v1 v2) = App <$> (g v1 >>= g') <*> (g v2 >>= g')
122122
g' (IfThenElse v1 v2 v3) = IfThenElse <$> (g v1 >>= g') <*> (g v2 >>= g') <*> (g v3 >>= g')
@@ -184,7 +184,7 @@ everywhereOnValuesM f g h = (f', g', h')
184184
g' (TypeClassDictionaryConstructorApp name v) = (TypeClassDictionaryConstructorApp name <$> g' v) >>= g
185185
g' (Accessor prop v) = (Accessor prop <$> g' v) >>= g
186186
g' (ObjectUpdate obj vs) = (ObjectUpdate <$> g' obj <*> traverse (sndM g') vs) >>= g
187-
g' (ObjectUpdateNested obj vs) = (ObjectUpdateNested <$> g' obj <*> traverse (sndM g') vs) >>= g
187+
g' (ObjectUpdateNested obj vs) = (ObjectUpdateNested <$> g' obj <*> traverse g' vs) >>= g
188188
g' (Abs name v) = (Abs name <$> g' v) >>= g
189189
g' (App v1 v2) = (App <$> g' v1 <*> g' v2) >>= g
190190
g' (IfThenElse v1 v2 v3) = (IfThenElse <$> g' v1 <*> g' v2 <*> g' v3) >>= g
@@ -257,7 +257,7 @@ everythingOnValues (<>) f g h i j = (f', g', h', i', j')
257257
g' v@(TypeClassDictionaryConstructorApp _ v1) = g v <> g' v1
258258
g' v@(Accessor _ v1) = g v <> g' v1
259259
g' v@(ObjectUpdate obj vs) = foldl (<>) (g v <> g' obj) (map (g' . snd) vs)
260-
g' v@(ObjectUpdateNested obj vs) = foldl (<>) (g v <> g' obj) (map (g' . snd) vs)
260+
g' v@(ObjectUpdateNested obj vs) = foldl (<>) (g v <> g' obj) (fmap g' vs)
261261
g' v@(Abs _ v1) = g v <> g' v1
262262
g' v@(App v1 v2) = g v <> g' v1 <> g' v2
263263
g' v@(IfThenElse v1 v2 v3) = g v <> g' v1 <> g' v2 <> g' v3
@@ -335,7 +335,7 @@ everythingWithContextOnValues s0 r0 (<>) f g h i j = (f'' s0, g'' s0, h'' s0, i'
335335
g' s (TypeClassDictionaryConstructorApp _ v1) = g'' s v1
336336
g' s (Accessor _ v1) = g'' s v1
337337
g' s (ObjectUpdate obj vs) = foldl (<>) (g'' s obj) (map (g'' s . snd) vs)
338-
g' s (ObjectUpdateNested obj vs) = foldl (<>) (g'' s obj) (map (g'' s . snd) vs)
338+
g' s (ObjectUpdateNested obj vs) = foldl (<>) (g'' s obj) (fmap (g'' s) vs)
339339
g' s (Abs _ v1) = g'' s v1
340340
g' s (App v1 v2) = g'' s v1 <> g'' s v2
341341
g' s (IfThenElse v1 v2 v3) = g'' s v1 <> g'' s v2 <> g'' s v3
@@ -415,7 +415,7 @@ everywhereWithContextOnValuesM s0 f g h i j = (f'' s0, g'' s0, h'' s0, i'' s0, j
415415
g' s (TypeClassDictionaryConstructorApp name v) = TypeClassDictionaryConstructorApp name <$> g'' s v
416416
g' s (Accessor prop v) = Accessor prop <$> g'' s v
417417
g' s (ObjectUpdate obj vs) = ObjectUpdate <$> g'' s obj <*> traverse (sndM (g'' s)) vs
418-
g' s (ObjectUpdateNested obj vs) = ObjectUpdateNested <$> g'' s obj <*> traverse (sndM (g'' s)) vs
418+
g' s (ObjectUpdateNested obj vs) = ObjectUpdateNested <$> g'' s obj <*> traverse (g'' s) vs
419419
g' s (Abs name v) = Abs name <$> g'' s v
420420
g' s (App v1 v2) = App <$> g'' s v1 <*> g'' s v2
421421
g' s (IfThenElse v1 v2 v3) = IfThenElse <$> g'' s v1 <*> g'' s v2 <*> g'' s v3
@@ -506,7 +506,7 @@ everythingWithScope f g h i j = (f'', g'', h'', i'', \s -> snd . j'' s)
506506
g' s (TypeClassDictionaryConstructorApp _ v1) = g'' s v1
507507
g' s (Accessor _ v1) = g'' s v1
508508
g' s (ObjectUpdate obj vs) = g'' s obj <> foldMap (g'' s . snd) vs
509-
g' s (ObjectUpdateNested obj vs) = g'' s obj <> foldMap (g'' s . snd) vs
509+
g' s (ObjectUpdateNested obj vs) = g'' s obj <> foldMap (g'' s) vs
510510
g' s (Abs (Left name) v1) =
511511
let s' = S.insert name s
512512
in g'' s' v1

src/Language/PureScript/Parser/Declarations.hs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@ import Prelude hiding (lex)
1717

1818
import Control.Applicative
1919
import Control.Arrow ((+++))
20+
import Control.Monad (foldM)
2021
import Control.Monad.Error.Class (MonadError(..))
2122
import Control.Parallel.Strategies (withStrategy, parList, rseq)
22-
import Data.Bifunctor (first)
2323
import Data.Functor (($>))
24-
import Data.List.NonEmpty (NonEmpty(..))
25-
import qualified Data.List.NonEmpty as N
2624
import Data.Maybe (fromMaybe)
25+
import qualified Data.Set as S
2726
import Data.Text (Text)
2827
import Language.PureScript.AST
2928
import Language.PureScript.Environment
@@ -399,13 +398,17 @@ parseInfixExpr
399398
parseHole :: TokenParser Expr
400399
parseHole = Hole <$> holeLit
401400

402-
parsePropertyUpdate :: TokenParser (NonEmpty PSString, Expr)
401+
parsePropertyUpdate :: TokenParser (PSString, PathNode Expr)
403402
parsePropertyUpdate = do
404403
name <- parseLabel
405-
rest <- P.many (indented *> dot *> indented *> parseLabel)
406-
_ <- indented *> equals
407-
value <- indented *> parseValue
408-
return (name :| rest, value)
404+
updates <- parseShallowUpdate <|> parseNestedUpdate
405+
return (name, updates)
406+
where
407+
parseShallowUpdate :: TokenParser (PathNode Expr)
408+
parseShallowUpdate = Leaf <$> (indented *> equals *> indented *> parseValue)
409+
410+
parseNestedUpdate :: TokenParser (PathNode Expr)
411+
parseNestedUpdate = Branch <$> parseUpdaterBodyFields
409412

410413
parseAccessor :: Expr -> TokenParser Expr
411414
parseAccessor (Constructor _) = P.unexpected "constructor"
@@ -455,11 +458,18 @@ parseValue = withSourceSpan PositionedValue
455458
]
456459
]
457460

458-
parseUpdaterBody :: Expr -> TokenParser Expr
459-
parseUpdaterBody v = objectUpdate <$> (indented *> braces (commaSep1 (indented *> parsePropertyUpdate)))
461+
parseUpdaterBodyFields :: TokenParser (PathTree Expr)
462+
parseUpdaterBodyFields = do
463+
updates <- indented *> braces (commaSep1 (indented *> parsePropertyUpdate))
464+
(_, tree) <- foldM insertUpdate (S.empty, []) updates
465+
return (PathTree (AssocList (reverse tree)))
460466
where
461-
objectUpdate xs | all (null . N.tail . fst) xs = ObjectUpdate v (map (first N.head) xs)
462-
| otherwise = ObjectUpdateNested v xs
467+
insertUpdate (seen, xs) (key, node)
468+
| S.member key seen = P.unexpected ("Duplicate key in record update: " ++ show key)
469+
| otherwise = return (S.insert key seen, (key, node) : xs)
470+
471+
parseUpdaterBody :: Expr -> TokenParser Expr
472+
parseUpdaterBody v = ObjectUpdateNested v <$> parseUpdaterBodyFields
463473

464474
parseAnonymousArgument :: TokenParser Expr
465475
parseAnonymousArgument = underscore *> pure AnonymousArgument

src/Language/PureScript/Pretty/Values.hs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import Prelude.Compat
1212
import Control.Arrow (second)
1313

1414
import qualified Data.Monoid as Monoid ((<>))
15-
import Data.List.NonEmpty (NonEmpty(..))
1615

1716
import qualified Data.Text as T
1817
import Data.Text (Text)
@@ -48,11 +47,8 @@ prettyPrintObject d = list '{' '}' prettyPrintObjectProperty
4847
prettyPrintObjectProperty :: (PSString, Maybe Expr) -> Box
4948
prettyPrintObjectProperty (key, value) = textT (prettyPrintObjectKey key Monoid.<> ": ") <> maybe (text "_") (prettyPrintValue (d - 1)) value
5049

51-
prettyPrintObjectUpdate :: forall k. (k -> Box) -> Int -> Expr -> [(k, Expr)] -> Box
52-
prettyPrintObjectUpdate printKey d o ps =
53-
prettyPrintValueAtom (d - 1) o `beforeWithSpace` list '{' '}' printEntry ps
54-
where
55-
printEntry (key, val) = printKey key <> text " = " <> prettyPrintValue (d - 1) val
50+
prettyPrintUpdateEntry :: Int -> PSString -> Expr -> Box
51+
prettyPrintUpdateEntry d key val = textT (prettyPrintObjectKey key) <> text " = " <> prettyPrintValue (d - 1) val
5652

5753
-- | Pretty-print an expression
5854
prettyPrintValue :: Int -> Expr -> Box
@@ -63,10 +59,12 @@ prettyPrintValue d (IfThenElse cond th el) =
6359
, text "else " <> prettyPrintValueAtom (d - 1) el
6460
])
6561
prettyPrintValue d (Accessor prop val) = prettyPrintValueAtom (d - 1) val `before` textT ("." Monoid.<> prettyPrintObjectKey prop)
66-
prettyPrintValue d (ObjectUpdate o ps) = prettyPrintObjectUpdate (textT . prettyPrintObjectKey) d o ps
67-
prettyPrintValue d (ObjectUpdateNested o ps) = prettyPrintObjectUpdate printPath d o ps where
68-
printPath (hd :| tl) = foldl combine (textT (prettyPrintObjectKey hd)) tl
69-
combine acc key = acc <> textT ("." Monoid.<> prettyPrintObjectKey key)
62+
prettyPrintValue d (ObjectUpdate o ps) = prettyPrintValueAtom (d - 1) o `beforeWithSpace` list '{' '}' (uncurry (prettyPrintUpdateEntry d)) ps
63+
prettyPrintValue d (ObjectUpdateNested o ps) = prettyPrintValueAtom (d - 1) o `beforeWithSpace` prettyPrintUpdate ps
64+
where
65+
prettyPrintUpdate (PathTree tree) = list '{' '}' printNode (runAssocList tree)
66+
printNode (key, Leaf val) = prettyPrintUpdateEntry d key val
67+
printNode (key, Branch val) = textT (prettyPrintObjectKey key) `beforeWithSpace` prettyPrintUpdate val
7068
prettyPrintValue d (App val arg) = prettyPrintValueAtom (d - 1) val `beforeWithSpace` prettyPrintValueAtom (d - 1) arg
7169
prettyPrintValue d (Abs (Left arg) val) = text ('\\' : T.unpack (showIdent arg) ++ " -> ") // moveRight 2 (prettyPrintValue (d - 1) val)
7270
prettyPrintValue d (Abs (Right arg) val) = text ('\\' : T.unpack (prettyPrintBinder arg) ++ " -> ") // moveRight 2 (prettyPrintValue (d - 1) val)

0 commit comments

Comments
 (0)