From 61e0309b0d1c37a8f7b0c2fcbf17d7f84d405209 Mon Sep 17 00:00:00 2001 From: Aaron VonderHaar Date: Mon, 28 Nov 2022 11:20:47 -0800 Subject: [PATCH 1/8] format: retain comments in array literals --- compiler/src/AST/Source.hs | 6 +++- compiler/src/AST/SourceComments.hs | 6 ++++ compiler/src/Canonicalize/Expression.hs | 2 +- compiler/src/Gren/Format.hs | 40 +++++++++++++++++++++---- compiler/src/Parse/Expression.hs | 15 ++++++---- tests/Integration/FormatSpec.hs | 25 ++++++++++++++++ 6 files changed, 81 insertions(+), 13 deletions(-) diff --git a/compiler/src/AST/Source.hs b/compiler/src/AST/Source.hs index d1909db2b..3636a39c8 100644 --- a/compiler/src/AST/Source.hs +++ b/compiler/src/AST/Source.hs @@ -8,6 +8,7 @@ module AST.Source Expr, Expr_ (..), VarType (..), + ArrayEntry, IfBranch, CaseBranch, Def (..), @@ -59,7 +60,7 @@ data Expr_ | Float EF.Float | Var VarType Name | VarQual VarType Name Name - | Array [Expr] + | Array [ArrayEntry] | Op Name | Negate Expr | Binops [(Expr, [Comment], A.Located Name)] Expr @@ -78,6 +79,9 @@ data Expr_ data VarType = LowVar | CapVar deriving (Show) +type ArrayEntry = + (Expr, SC.ArrayEntryComments) + type IfBranch = (Expr, Expr, SC.IfBranchComments) diff --git a/compiler/src/AST/SourceComments.hs b/compiler/src/AST/SourceComments.hs index fb5aa52d8..c691d807f 100644 --- a/compiler/src/AST/SourceComments.hs +++ b/compiler/src/AST/SourceComments.hs @@ -89,6 +89,12 @@ data ValueComments = ValueComments -- Expressions +data ArrayEntryComments = ArrayEntryComments + { _beforeArrayEntry :: [Comment], + _afterArrayEntry :: [Comment] + } + deriving (Show) + data LambdaComments = LambdaComments { _beforeArrow :: [Comment], _afterArrow :: [Comment] diff --git a/compiler/src/Canonicalize/Expression.hs b/compiler/src/Canonicalize/Expression.hs index bd8b811e3..c1efb06e3 100644 --- a/compiler/src/Canonicalize/Expression.hs +++ b/compiler/src/Canonicalize/Expression.hs @@ -67,7 +67,7 @@ canonicalize env (A.At region expression) = Src.LowVar -> findVarQual region env prefix name Src.CapVar -> toVarCtor name <$> Env.findCtorQual region env prefix name Src.Array exprs -> - Can.Array <$> traverse (canonicalize env) exprs + Can.Array <$> traverse (canonicalize env) (fmap fst exprs) Src.Op op -> do (Env.Binop _ home name annotation _ _) <- Env.findBinop region env op diff --git a/compiler/src/Gren/Format.hs b/compiler/src/Gren/Format.hs index 42b0169cb..e7d35beba 100644 --- a/compiler/src/Gren/Format.hs +++ b/compiler/src/Gren/Format.hs @@ -68,16 +68,28 @@ spaceOrIndentForce :: Bool -> NonEmpty Block -> Block spaceOrIndentForce forceMultiline = Block.rowOrIndentForce forceMultiline (Just Block.space) group :: Char -> Char -> Char -> Bool -> [Block] -> Block -group open _ close _ [] = Block.line $ Block.char7 open <> Block.char7 close -group open sep close forceMultiline (first : rest) = +group open sep close forceMultiline = groupWithBlankLines open sep close forceMultiline . fmap (0,) + +-- | NOTE: The blankLines number for the first entry is always ignored. +groupWithBlankLines :: Char -> Char -> Char -> Bool -> [(Int, Block)] -> Block +groupWithBlankLines open _ close _ [] = Block.line $ Block.char7 open <> Block.char7 close +groupWithBlankLines open sep close forceMultiline ((_, first) : rest) = Block.rowOrStackForce forceMultiline (Just Block.space) [ Block.rowOrStackForce forceMultiline Nothing $ - Block.prefix 2 (Block.char7 open <> Block.space) first - :| fmap (Block.prefix 2 (Block.char7 sep <> Block.space)) (rest), + formatEntry open (0, first) + :| fmap (formatEntry sep) (rest), Block.line (Block.char7 close) ] + where + formatEntry char (0, entry) = + Block.prefix 2 (Block.char7 char <> Block.space) entry + formatEntry char (blankLines, entry) = + Block.stack $ + NonEmpty.prependList (replicate blankLines Block.blankLine) $ + NonEmpty.singleton $ + Block.prefix 2 (Block.char7 char <> Block.space) entry surround :: Char -> Char -> Block -> Block surround open close block = @@ -155,6 +167,15 @@ withCommentsStackAround before after block = (Nothing, Just afterBlock) -> Block.stack [block, afterBlock] (Just beforeBlock, Just afterBlock) -> Block.stack [beforeBlock, block, afterBlock] +withCommentsStackAroundIndented :: [Src.Comment] -> [Src.Comment] -> Block -> Block +withCommentsStackAroundIndented [] [] block = block +withCommentsStackAroundIndented before after block = + case (formatCommentBlock before, formatCommentBlock after) of + (Nothing, Nothing) -> block + (Just beforeBlock, Nothing) -> Block.stack [beforeBlock, block] + (Nothing, Just afterBlock) -> Block.stack [block, Block.indent afterBlock] + (Just beforeBlock, Just afterBlock) -> Block.stack [beforeBlock, block, Block.indent afterBlock] + -- -- AST -> Block -- @@ -518,8 +539,15 @@ formatExpr = \case utf8 ns <> Block.char7 '.' <> utf8 name Src.Array exprs -> NoExpressionParens $ - group '[' ',' ']' True $ - fmap (exprParensNone . formatExpr . A.toValue) exprs + groupWithBlankLines '[' ',' ']' True $ + fmap formatArrayEntry exprs + where + formatArrayEntry (entryExpr, SC.ArrayEntryComments commentsBefore commentsAfter) = + ( if List.null commentsBefore then 0 else 1, + withCommentsStackAroundIndented commentsBefore commentsAfter $ + exprParensNone $ + formatExpr (A.toValue entryExpr) + ) Src.Op name -> NoExpressionParens $ Block.line $ diff --git a/compiler/src/Parse/Expression.hs b/compiler/src/Parse/Expression.hs index 781329104..dcebd3c91 100644 --- a/compiler/src/Parse/Expression.hs +++ b/compiler/src/Parse/Expression.hs @@ -145,27 +145,32 @@ array :: A.Position -> Parser E.Expr Src.Expr array start = inContext E.Array (word1 0x5B {-[-} E.Start) $ do - Space.chompAndCheckIndent E.ArraySpace E.ArrayIndentOpen + commentsAfterOpenBrace <- Space.chompAndCheckIndent E.ArraySpace E.ArrayIndentOpen oneOf E.ArrayOpen [ do - ((entry, commentsAfterEntry), end) <- specialize E.ArrayExpr expression + ((expr, commentsAfterExpr), end) <- specialize E.ArrayExpr expression Space.checkIndent end E.ArrayIndentEnd + let entryComments = SC.ArrayEntryComments commentsAfterOpenBrace commentsAfterExpr + let entry = (expr, entryComments) chompArrayEnd start [entry], do + -- TODO: comments in an empty array are dropped; what to do with these? word1 0x5D {-]-} E.ArrayOpen addEnd start (Src.Array []) ] -chompArrayEnd :: A.Position -> [Src.Expr] -> Parser E.Array Src.Expr +chompArrayEnd :: A.Position -> [Src.ArrayEntry] -> Parser E.Array Src.Expr chompArrayEnd start entries = oneOf E.ArrayEnd [ do word1 0x2C {-,-} E.ArrayEnd - Space.chompAndCheckIndent E.ArraySpace E.ArrayIndentExpr - ((entry, commentsAfterEntry), end) <- specialize E.ArrayExpr expression + commentsAfterComma <- Space.chompAndCheckIndent E.ArraySpace E.ArrayIndentExpr + ((expr, commentsAfterExpr), end) <- specialize E.ArrayExpr expression Space.checkIndent end E.ArrayIndentEnd + let entryComments = SC.ArrayEntryComments commentsAfterComma commentsAfterExpr + let entry = (expr, entryComments) chompArrayEnd start (entry : entries), do word1 0x5D {-]-} E.ArrayEnd diff --git a/tests/Integration/FormatSpec.hs b/tests/Integration/FormatSpec.hs index 029125e06..921c0a0dc 100644 --- a/tests/Integration/FormatSpec.hs +++ b/tests/Integration/FormatSpec.hs @@ -291,6 +291,31 @@ spec = do ] describe "expressions" $ do + describe "array literals" $ do + it "formats" $ + ["[1,2,3]"] + `shouldFormatExpressionAs` [ "[ 1", + ", 2", + ", 3", + "]" + ] + + it "formats comments" $ + ["[{-A-}1{-B-},{-C-}2{-D-},{-E-}3{-F-}]"] + `shouldFormatExpressionAs` [ "[ {- A -}", + " 1", + " {- B -}", + "", + ", {- C -}", + " 2", + " {- D -}", + "", + ", {- E -}", + " 3", + " {- F -}", + "]" + ] + describe "lambda" $ do it "formats comments" $ ["\\{-A-}x{-B-}y{-C-}->{-D-}[]"] From 4297c7b5e08d1faeefb5bdce70b07fcd23e2d7e8 Mon Sep 17 00:00:00 2001 From: Aaron VonderHaar Date: Mon, 28 Nov 2022 11:35:05 -0800 Subject: [PATCH 2/8] format: retain comments in array patterns --- compiler/src/AST/Source.hs | 5 ++++- compiler/src/AST/SourceComments.hs | 8 ++++++++ compiler/src/Canonicalize/Expression.hs | 4 ++-- compiler/src/Canonicalize/Pattern.hs | 2 +- compiler/src/Gren/Format.hs | 7 ++++++- compiler/src/Parse/Pattern.hs | 15 ++++++++++----- tests/Integration/FormatSpec.hs | 8 ++++++++ 7 files changed, 39 insertions(+), 10 deletions(-) diff --git a/compiler/src/AST/Source.hs b/compiler/src/AST/Source.hs index 3636a39c8..3e658cc3c 100644 --- a/compiler/src/AST/Source.hs +++ b/compiler/src/AST/Source.hs @@ -16,6 +16,7 @@ module AST.Source Pattern_ (..), RecordFieldPattern, RecordFieldPattern_ (..), + PArrayEntry, Type, Type_ (..), SourceOrder, @@ -106,7 +107,7 @@ data Pattern_ | PAlias Pattern (A.Located Name) | PCtor A.Region Name [([Comment], Pattern)] | PCtorQual A.Region Name Name [([Comment], Pattern)] - | PArray [Pattern] + | PArray [PArrayEntry] | PChr ES.String | PStr ES.String | PInt Int @@ -117,6 +118,8 @@ type RecordFieldPattern = A.Located RecordFieldPattern_ data RecordFieldPattern_ = RFPattern (A.Located Name) Pattern deriving (Show) +type PArrayEntry = (Pattern, SC.PArrayEntryComments) + -- TYPE type Type = diff --git a/compiler/src/AST/SourceComments.hs b/compiler/src/AST/SourceComments.hs index c691d807f..a252be090 100644 --- a/compiler/src/AST/SourceComments.hs +++ b/compiler/src/AST/SourceComments.hs @@ -134,3 +134,11 @@ data CaseBranchComments = CaseBranchComments _afterBranchBody :: [Comment] } deriving (Show) + +-- Patterns + +data PArrayEntryComments = PArrayEntryComments + { _beforePArrayEntry :: [Comment], + _afterPArrayEntry :: [Comment] + } + deriving (Show) diff --git a/compiler/src/Canonicalize/Expression.hs b/compiler/src/Canonicalize/Expression.hs index c1efb06e3..f9c4e5bb6 100644 --- a/compiler/src/Canonicalize/Expression.hs +++ b/compiler/src/Canonicalize/Expression.hs @@ -250,7 +250,7 @@ addBindingsHelp bindings (A.At region pattern) = Src.PCtorQual _ _ _ patterns -> List.foldl' addBindingsHelp bindings (fmap snd patterns) Src.PArray patterns -> - List.foldl' addBindingsHelp bindings patterns + List.foldl' addBindingsHelp bindings (fmap fst patterns) Src.PAlias aliasPattern (A.At nameRegion name) -> Dups.insert name nameRegion nameRegion $ addBindingsHelp bindings aliasPattern @@ -358,7 +358,7 @@ getPatternNames names (A.At region pattern) = Src.PAlias ptrn name -> getPatternNames (name : names) ptrn Src.PCtor _ _ args -> List.foldl' getPatternNames names (fmap snd args) Src.PCtorQual _ _ _ args -> List.foldl' getPatternNames names (fmap snd args) - Src.PArray patterns -> List.foldl' getPatternNames names patterns + Src.PArray patterns -> List.foldl' getPatternNames names (fmap fst patterns) Src.PChr _ -> names Src.PStr _ -> names Src.PInt _ -> names diff --git a/compiler/src/Canonicalize/Pattern.hs b/compiler/src/Canonicalize/Pattern.hs index 23a8f3f05..bc496b1bb 100644 --- a/compiler/src/Canonicalize/Pattern.hs +++ b/compiler/src/Canonicalize/Pattern.hs @@ -69,7 +69,7 @@ canonicalize env (A.At region pattern) = Src.PCtorQual nameRegion home name patterns -> canonicalizeCtor env region name (fmap snd patterns) =<< Env.findCtorQual nameRegion env home name Src.PArray patterns -> - Can.PArray <$> canonicalizeList env patterns + Can.PArray <$> canonicalizeList env (fmap fst patterns) Src.PAlias ptrn (A.At reg name) -> do cpattern <- canonicalize env ptrn diff --git a/compiler/src/Gren/Format.hs b/compiler/src/Gren/Format.hs index e7d35beba..b226359a3 100644 --- a/compiler/src/Gren/Format.hs +++ b/compiler/src/Gren/Format.hs @@ -917,7 +917,12 @@ formatPattern = \case Src.PArray items -> NoPatternParens $ group '[' ',' ']' False $ - fmap (patternParensNone . formatPattern . A.toValue) items + fmap formatArrayPatternEntry items + where + formatArrayPatternEntry (pattern, SC.PArrayEntryComments commentsBefore commentsAfter) = + withCommentsAround commentsBefore commentsAfter $ + patternParensNone $ + formatPattern (A.toValue pattern) Src.PChr char -> NoPatternParens $ formatString StringStyleChar char diff --git a/compiler/src/Parse/Pattern.hs b/compiler/src/Parse/Pattern.hs index 0dce9bc41..aa5014f69 100644 --- a/compiler/src/Parse/Pattern.hs +++ b/compiler/src/Parse/Pattern.hs @@ -13,6 +13,7 @@ module Parse.Pattern where import AST.Source qualified as Src +import AST.SourceComments qualified as SC import Data.Name qualified as Name import Data.Utf8 qualified as Utf8 import Foreign.Ptr (plusPtr) @@ -181,22 +182,26 @@ array start = [ do ((pattern, commentsAfterPattern), end) <- P.specialize E.PArrayExpr expression Space.checkIndent end E.PArrayIndentEnd - arrayHelp start [pattern], + let entryComments = SC.PArrayEntryComments commentsAfterOpenBracket commentsAfterPattern + let entry = (pattern, entryComments) + arrayHelp start [entry], do word1 0x5D {-]-} E.PArrayEnd addEnd start (Src.PArray []) ] -arrayHelp :: A.Position -> [Src.Pattern] -> Parser E.PArray Src.Pattern +arrayHelp :: A.Position -> [Src.PArrayEntry] -> Parser E.PArray Src.Pattern arrayHelp start patterns = oneOf E.PArrayEnd [ do word1 0x2C {-,-} E.PArrayEnd - Space.chompAndCheckIndent E.PArraySpace E.PArrayIndentExpr - ((pattern, commentsAfter), end) <- P.specialize E.PArrayExpr expression + commentsAfterComma <- Space.chompAndCheckIndent E.PArraySpace E.PArrayIndentExpr + ((pattern, commentsAfterPattern), end) <- P.specialize E.PArrayExpr expression Space.checkIndent end E.PArrayIndentEnd - arrayHelp start (pattern : patterns), + let entryComments = SC.PArrayEntryComments commentsAfterComma commentsAfterPattern + let entry = (pattern, entryComments) + arrayHelp start (entry : patterns), do word1 0x5D {-]-} E.PArrayEnd addEnd start (Src.PArray (reverse patterns)) diff --git a/tests/Integration/FormatSpec.hs b/tests/Integration/FormatSpec.hs index 921c0a0dc..fb1748682 100644 --- a/tests/Integration/FormatSpec.hs +++ b/tests/Integration/FormatSpec.hs @@ -464,6 +464,14 @@ spec = do assertFormattedExpression ["({- A -} x)"] + describe "patterns" $ do + describe "array patterns" $ do + it "formats comments" $ + ["f [{-A-}1{-B-},{-C-}2{-D-}] = {}"] + `shouldFormatModuleBodyAs` [ "f [ {- A -} 1 {- B -}, {- C -} 2 {- D -} ] =", + " {}" + ] + assertFormatted :: [Text] -> IO () assertFormatted lines_ = lines_ `shouldFormatAs` lines_ From 105efdec9a94cca3ade2dc23ece10024f518416e Mon Sep 17 00:00:00 2001 From: Aaron VonderHaar Date: Mon, 28 Nov 2022 20:20:53 -0800 Subject: [PATCH 3/8] format: retain comments in record expressions --- compiler/src/AST/Source.hs | 8 +++- compiler/src/AST/SourceComments.hs | 8 ++++ compiler/src/Canonicalize/Environment/Dups.hs | 12 +++--- compiler/src/Canonicalize/Type.hs | 4 +- compiler/src/Gren/Format.hs | 24 +++++++---- compiler/src/Parse/Expression.hs | 42 ++++++++++--------- tests/Integration/FormatSpec.hs | 9 ++++ 7 files changed, 71 insertions(+), 36 deletions(-) diff --git a/compiler/src/AST/Source.hs b/compiler/src/AST/Source.hs index 3e658cc3c..d5a561644 100644 --- a/compiler/src/AST/Source.hs +++ b/compiler/src/AST/Source.hs @@ -11,6 +11,7 @@ module AST.Source ArrayEntry, IfBranch, CaseBranch, + RecordField, Def (..), Pattern, Pattern_ (..), @@ -72,8 +73,8 @@ data Expr_ | Case Expr [CaseBranch] SC.CaseComments | Accessor Name | Access Expr (A.Located Name) - | Update Expr [(A.Located Name, Expr)] - | Record [(A.Located Name, Expr)] + | Update Expr [RecordField] + | Record [RecordField] | Parens [Comment] Expr [Comment] deriving (Show) @@ -89,6 +90,9 @@ type IfBranch = type CaseBranch = (Pattern, Expr, SC.CaseBranchComments) +type RecordField = + (A.Located Name, Expr, SC.RecordFieldComments) + -- DEFINITIONS data Def diff --git a/compiler/src/AST/SourceComments.hs b/compiler/src/AST/SourceComments.hs index a252be090..fdf868ac2 100644 --- a/compiler/src/AST/SourceComments.hs +++ b/compiler/src/AST/SourceComments.hs @@ -135,6 +135,14 @@ data CaseBranchComments = CaseBranchComments } deriving (Show) +data RecordFieldComments = RecordFieldComments + { _beforeFieldName :: [Comment], + _afterFieldName :: [Comment], + _beforeFieldValue :: [Comment], + _afterFieldValue :: [Comment] + } + deriving (Show) + -- Patterns data PArrayEntryComments = PArrayEntryComments diff --git a/compiler/src/Canonicalize/Environment/Dups.hs b/compiler/src/Canonicalize/Environment/Dups.hs index 35b238e56..2106e5da3 100644 --- a/compiler/src/Canonicalize/Environment/Dups.hs +++ b/compiler/src/Canonicalize/Environment/Dups.hs @@ -52,20 +52,20 @@ detectHelp toError name values = -- CHECK FIELDS -checkFields :: [(A.Located Name.Name, a)] -> Result.Result i w Error.Error (Map.Map Name.Name a) +checkFields :: [(A.Located Name.Name, a, comments)] -> Result.Result i w Error.Error (Map.Map Name.Name a) checkFields fields = detect Error.DuplicateField (foldr addField none fields) -addField :: (A.Located Name.Name, a) -> Dict a -> Dict a -addField (A.At region name, value) dups = +addField :: (A.Located Name.Name, a, comments) -> Dict a -> Dict a +addField (A.At region name, value, _) dups = Map.insertWith OneOrMore.more name (OneOrMore.one (Info region value)) dups -checkFields' :: (A.Region -> a -> b) -> [(A.Located Name.Name, a)] -> Result.Result i w Error.Error (Map.Map Name.Name b) +checkFields' :: (A.Region -> a -> b) -> [(A.Located Name.Name, a, comments)] -> Result.Result i w Error.Error (Map.Map Name.Name b) checkFields' toValue fields = detect Error.DuplicateField (foldr (addField' toValue) none fields) -addField' :: (A.Region -> a -> b) -> (A.Located Name.Name, a) -> Dict b -> Dict b -addField' toValue (A.At region name, value) dups = +addField' :: (A.Region -> a -> b) -> (A.Located Name.Name, a, comments) -> Dict b -> Dict b +addField' toValue (A.At region name, value, _) dups = Map.insertWith OneOrMore.more name (OneOrMore.one (Info region (toValue region value))) dups -- BUILDING DICTIONARIES diff --git a/compiler/src/Canonicalize/Type.hs b/compiler/src/Canonicalize/Type.hs index 0060b0955..3a99be9ce 100644 --- a/compiler/src/Canonicalize/Type.hs +++ b/compiler/src/Canonicalize/Type.hs @@ -53,11 +53,11 @@ canonicalize env (A.At typeRegion tipe) = cfields <- sequenceA =<< Dups.checkFields (canonicalizeFields env fields) return $ Can.TRecord cfields (fmap A.toValue ext) -canonicalizeFields :: Env.Env -> [(A.Located Name.Name, Src.Type)] -> [(A.Located Name.Name, Result i w Can.FieldType)] +canonicalizeFields :: Env.Env -> [(A.Located Name.Name, Src.Type)] -> [(A.Located Name.Name, Result i w Can.FieldType, ())] canonicalizeFields env fields = let len = fromIntegral (length fields) canonicalizeField index (name, srcType) = - (name, Can.FieldType index <$> canonicalize env srcType) + (name, Can.FieldType index <$> canonicalize env srcType, ()) in zipWith canonicalizeField [0 .. len] fields -- CANONICALIZE TYPE diff --git a/compiler/src/Gren/Format.hs b/compiler/src/Gren/Format.hs index b226359a3..446790ed5 100644 --- a/compiler/src/Gren/Format.hs +++ b/compiler/src/Gren/Format.hs @@ -712,20 +712,30 @@ formatExpr = \case (exprParensNone $ formatExpr $ A.toValue base) (fmap formatField $ first :| rest) where - formatField (field, expr) = + formatField (field, expr, comments) = ( utf8 $ A.toValue field, exprParensNone $ formatExpr (A.toValue expr) ) Src.Record fields -> NoExpressionParens $ - group '{' ',' '}' True $ + groupWithBlankLines '{' ',' '}' True $ fmap formatField fields where - formatField (name, expr) = - spaceOrIndent - [ Block.line $ utf8 (A.toValue name) <> Block.space <> Block.char7 '=', - exprParensNone $ formatExpr (A.toValue expr) - ] + formatField (name, expr, SC.RecordFieldComments commentsBeforeName commentsAfterName commentsBeforeValue commentsAfterValue) = + ( if List.null commentsBeforeName then 0 else 1, + withCommentsStackBefore commentsBeforeName $ + spaceOrIndent + [ spaceOrIndent + [ withCommentsAround [] commentsAfterName $ + Block.line $ + utf8 (A.toValue name), + Block.line $ Block.char7 '=' + ], + withCommentsAround commentsBeforeValue commentsAfterValue $ + exprParensNone $ + formatExpr (A.toValue expr) + ] + ) Src.Parens [] expr [] -> formatExpr $ A.toValue expr Src.Parens commentsBefore expr commentsAfter -> diff --git a/compiler/src/Parse/Expression.hs b/compiler/src/Parse/Expression.hs index dcebd3c91..8e74a7e13 100644 --- a/compiler/src/Parse/Expression.hs +++ b/compiler/src/Parse/Expression.hs @@ -183,31 +183,36 @@ record :: A.Position -> Parser E.Expr Src.Expr record start = inContext E.Record (word1 0x7B {- { -} E.Start) $ do - Space.chompAndCheckIndent E.RecordSpace E.RecordIndentOpen + commentsAfterOpenBrace <- Space.chompAndCheckIndent E.RecordSpace E.RecordIndentOpen oneOf E.RecordOpen [ do + -- TODO: what to do with comments in empty record word1 0x7D {-}-} E.RecordEnd addEnd start (Src.Record []), do - expr <- specialize E.RecordUpdateExpr term - Space.chompAndCheckIndent E.RecordSpace E.RecordIndentEquals + firstTerm <- specialize E.RecordUpdateExpr term + commentsAfterFirstTerm <- Space.chompAndCheckIndent E.RecordSpace E.RecordIndentEquals oneOf E.RecordEquals [ do word1 0x7C {- vertical bar -} E.RecordPipe - Space.chompAndCheckIndent E.RecordSpace E.RecordIndentField - firstField <- chompField + commentsAfterBar <- Space.chompAndCheckIndent E.RecordSpace E.RecordIndentField + firstField <- chompField commentsAfterBar + -- TODO: use commentsAfterOpenBrace + -- TODO: use commentsAfterFirstTerm fields <- chompFields [firstField] - addEnd start (Src.Update expr fields), + addEnd start (Src.Update firstTerm fields), do word1 0x3D {-=-} E.RecordEquals - Space.chompAndCheckIndent E.RecordSpace E.RecordIndentExpr + commentsAfterEquals <- Space.chompAndCheckIndent E.RecordSpace E.RecordIndentExpr ((value, commentsAfterValue), end) <- specialize E.RecordExpr expression Space.checkIndent end E.RecordIndentEnd - case expr of + case firstTerm of A.At exprRegion (Src.Var Src.LowVar name) -> do - fields <- chompFields [(A.At exprRegion name, value)] + let firstFieldComments = SC.RecordFieldComments commentsAfterOpenBrace commentsAfterFirstTerm commentsAfterEquals commentsAfterValue + let firstField = (A.At exprRegion name, value, firstFieldComments) + fields <- chompFields [firstField] addEnd start (Src.Record fields) A.At (A.Region (A.Position row col) _) _ -> P.Parser $ \_ _ _ _ eerr -> @@ -215,32 +220,31 @@ record start = ] ] -type Field = (A.Located Name.Name, Src.Expr) - -chompFields :: [Field] -> Parser E.Record [Field] +chompFields :: [Src.RecordField] -> Parser E.Record [Src.RecordField] chompFields fields = oneOf E.RecordEnd [ do word1 0x2C {-,-} E.RecordEnd - Space.chompAndCheckIndent E.RecordSpace E.RecordIndentField - f <- chompField + commentsAfterComma <- Space.chompAndCheckIndent E.RecordSpace E.RecordIndentField + f <- chompField commentsAfterComma chompFields (f : fields), do word1 0x7D {-}-} E.RecordEnd return (reverse fields) ] -chompField :: Parser E.Record Field -chompField = +chompField :: [Src.Comment] -> Parser E.Record Src.RecordField +chompField commentsBefore = do key <- addLocation (Var.lower E.RecordField) - Space.chompAndCheckIndent E.RecordSpace E.RecordIndentEquals + commentsAfterFieldName <- Space.chompAndCheckIndent E.RecordSpace E.RecordIndentEquals word1 0x3D {-=-} E.RecordEquals - Space.chompAndCheckIndent E.RecordSpace E.RecordIndentExpr + commentsAfterEquals <- Space.chompAndCheckIndent E.RecordSpace E.RecordIndentExpr ((value, commentsAfter), end) <- specialize E.RecordExpr expression Space.checkIndent end E.RecordIndentEnd - return (key, value) + let comments = SC.RecordFieldComments commentsBefore commentsAfterFieldName commentsAfterEquals commentsAfter + return (key, value, comments) -- EXPRESSIONS diff --git a/tests/Integration/FormatSpec.hs b/tests/Integration/FormatSpec.hs index fb1748682..5d327ed89 100644 --- a/tests/Integration/FormatSpec.hs +++ b/tests/Integration/FormatSpec.hs @@ -451,6 +451,15 @@ spec = do ", b = 2", "}" ] + it "formats comments" $ + ["{{-A-}a{-B-}={-C-}1{-D-},{-E-}b{-F-}={-G-}2{-H-}}"] + `shouldFormatExpressionAs` [ "{ {- A -}", + " a {- B -} = {- C -} 1 {- D -}", + "", + ", {- E -}", + " b {- F -} = {- G -} 2 {- H -}", + "}" + ] describe "parentheses" $ do it "removes unnecessary parentheses" $ From b8e11fc2f95a8ef237f5171b6a0ad97f0344da03 Mon Sep 17 00:00:00 2001 From: Aaron VonderHaar Date: Mon, 28 Nov 2022 20:24:53 -0800 Subject: [PATCH 4/8] format: Backfill test for record update expressions --- tests/Integration/FormatSpec.hs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/Integration/FormatSpec.hs b/tests/Integration/FormatSpec.hs index 5d327ed89..1b7024236 100644 --- a/tests/Integration/FormatSpec.hs +++ b/tests/Integration/FormatSpec.hs @@ -460,6 +460,14 @@ spec = do " b {- F -} = {- G -} 2 {- H -}", "}" ] + describe "record update" $ do + it "formats" $ + ["{base|a=1,b=2}"] + `shouldFormatExpressionAs` [ "{ base", + " | a = 1", + " , b = 2", + "}" + ] describe "parentheses" $ do it "removes unnecessary parentheses" $ From c1fd0132820dddd8988d5e947951f978be88e28e Mon Sep 17 00:00:00 2001 From: Aaron VonderHaar Date: Mon, 28 Nov 2022 21:29:28 -0800 Subject: [PATCH 5/8] format: retain comments in record update expressions --- compiler/src/AST/Source.hs | 2 +- compiler/src/AST/SourceComments.hs | 6 +++ compiler/src/Canonicalize/Expression.hs | 2 +- compiler/src/Gren/Format.hs | 65 +++++++++++++------------ compiler/src/Parse/Expression.hs | 5 +- tests/Integration/FormatSpec.hs | 12 +++++ tests/Parse/RecordUpdateSpec.hs | 2 +- 7 files changed, 58 insertions(+), 36 deletions(-) diff --git a/compiler/src/AST/Source.hs b/compiler/src/AST/Source.hs index d5a561644..ea58b9668 100644 --- a/compiler/src/AST/Source.hs +++ b/compiler/src/AST/Source.hs @@ -73,7 +73,7 @@ data Expr_ | Case Expr [CaseBranch] SC.CaseComments | Accessor Name | Access Expr (A.Located Name) - | Update Expr [RecordField] + | Update Expr [RecordField] SC.UpdateComments | Record [RecordField] | Parens [Comment] Expr [Comment] deriving (Show) diff --git a/compiler/src/AST/SourceComments.hs b/compiler/src/AST/SourceComments.hs index fdf868ac2..67edade26 100644 --- a/compiler/src/AST/SourceComments.hs +++ b/compiler/src/AST/SourceComments.hs @@ -135,6 +135,12 @@ data CaseBranchComments = CaseBranchComments } deriving (Show) +data UpdateComments = UpdateComments + { _beforeBase :: [Comment], + _afterBase :: [Comment] + } + deriving (Show) + data RecordFieldComments = RecordFieldComments { _beforeFieldName :: [Comment], _afterFieldName :: [Comment], diff --git a/compiler/src/Canonicalize/Expression.hs b/compiler/src/Canonicalize/Expression.hs index f9c4e5bb6..8ad91c8d0 100644 --- a/compiler/src/Canonicalize/Expression.hs +++ b/compiler/src/Canonicalize/Expression.hs @@ -110,7 +110,7 @@ canonicalize env (A.At region expression) = Can.Access <$> canonicalize env record <*> Result.ok field - Src.Update baseRecord fields -> + Src.Update baseRecord fields _ -> let makeCanFields = Dups.checkFields' (\r t -> Can.FieldUpdate r <$> canonicalize env t) fields in Can.Update diff --git a/compiler/src/Gren/Format.hs b/compiler/src/Gren/Format.hs index 446790ed5..4d4caceef 100644 --- a/compiler/src/Gren/Format.hs +++ b/compiler/src/Gren/Format.hs @@ -17,7 +17,8 @@ import Data.List.NonEmpty (NonEmpty ((:|)), nonEmpty) import Data.List.NonEmpty qualified as NonEmpty import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map -import Data.Maybe (catMaybes) +import Data.Maybe (catMaybes, maybeToList) +import Data.Maybe qualified as Maybe import Data.Name (Name) import Data.Semigroup (sconcat) import Data.Utf8 qualified as Utf8 @@ -102,43 +103,43 @@ surround open close block = parens :: Block -> Block parens = surround '(' ')' -extendedGroup :: Char -> Char -> Char -> Char -> Char -> Block -> NonEmpty (Block.Line, Block) -> Block +extendedGroup :: Char -> Char -> Char -> Char -> Char -> Block -> NonEmpty (Maybe Block, Block, Block) -> Block extendedGroup open baseSep sep fieldSep close base fields = case fields of (single :| []) -> spaceOrStack [ spaceOrIndent - [ spaceOrIndent - [ Block.line $ Block.char7 open, - base - ], - formatField baseSep single + [ formattedBase, + formatField True baseSep single ], Block.line $ Block.char7 close ] (first :| rest) -> Block.stack - [ spaceOrIndent - [ Block.line $ Block.char7 open, - base - ], + [ formattedBase, Block.indent $ Block.stack $ - formatField baseSep first - :| fmap (formatField sep) rest, + formatField True baseSep first + :| fmap (formatField False sep) rest, Block.line $ Block.char7 close ] where - formatField punc (key, value) = - spaceOrIndent - [ Block.line $ - Block.char7 punc - <> Block.space - <> key - <> Block.space - <> Block.char7 fieldSep, - value - ] + formattedBase = + Block.prefix 2 (Block.char7 open <> Block.space) base + + formatField isFirst punc (before, key, value) = + Block.stack $ + NonEmpty.prependList (if isFirst || Maybe.isNothing before then [] else [Block.blankLine]) $ + NonEmpty.singleton $ + Block.prefix 2 (Block.char7 punc <> Block.space) $ + Block.stack $ + NonEmpty.prependList (maybeToList before) $ + NonEmpty.singleton $ + spaceOrIndent + [ key, + Block.line $ Block.char7 fieldSep, + value + ] withCommentsBefore :: [Src.Comment] -> Block -> Block withCommentsBefore before = withCommentsAround before [] @@ -699,9 +700,9 @@ formatExpr = \case Src.Access expr field -> NoExpressionParens $ Block.addSuffix (Block.char7 '.' <> utf8 (A.toValue field)) (exprParensProtectSpaces $ formatExpr $ A.toValue expr) - Src.Update base [] -> + Src.Update base [] _ -> formatExpr $ A.toValue base - Src.Update base (first : rest) -> + Src.Update base (first : rest) (SC.UpdateComments commentsBeforeBase commentsAfterBase) -> NoExpressionParens $ extendedGroup '{' @@ -709,12 +710,15 @@ formatExpr = \case ',' '=' '}' - (exprParensNone $ formatExpr $ A.toValue base) + (withCommentsStackAround commentsBeforeBase commentsAfterBase $ exprParensNone $ formatExpr $ A.toValue base) (fmap formatField $ first :| rest) where - formatField (field, expr, comments) = - ( utf8 $ A.toValue field, - exprParensNone $ formatExpr (A.toValue expr) + formatField (field, expr, SC.RecordFieldComments commentsBeforeName commentsAfterName commentsBeforeValue commentsAfterValue) = + ( formatCommentBlock commentsBeforeName, + withCommentsAround [] commentsAfterName $ Block.line $ utf8 $ A.toValue field, + withCommentsAround commentsBeforeValue commentsAfterValue $ + exprParensNone $ + formatExpr (A.toValue expr) ) Src.Record fields -> NoExpressionParens $ @@ -860,7 +864,8 @@ formatType = \case (fmap formatField $ first :| rest) where formatField (field, type_) = - ( utf8 $ A.toValue field, + ( Nothing, + Block.line $ utf8 $ A.toValue field, typeParensNone $ formatType $ A.toValue type_ ) diff --git a/compiler/src/Parse/Expression.hs b/compiler/src/Parse/Expression.hs index 8e74a7e13..a6240dcca 100644 --- a/compiler/src/Parse/Expression.hs +++ b/compiler/src/Parse/Expression.hs @@ -199,10 +199,9 @@ record start = word1 0x7C {- vertical bar -} E.RecordPipe commentsAfterBar <- Space.chompAndCheckIndent E.RecordSpace E.RecordIndentField firstField <- chompField commentsAfterBar - -- TODO: use commentsAfterOpenBrace - -- TODO: use commentsAfterFirstTerm + let comments = SC.UpdateComments commentsAfterOpenBrace commentsAfterFirstTerm fields <- chompFields [firstField] - addEnd start (Src.Update firstTerm fields), + addEnd start (Src.Update firstTerm fields comments), do word1 0x3D {-=-} E.RecordEquals commentsAfterEquals <- Space.chompAndCheckIndent E.RecordSpace E.RecordIndentExpr diff --git a/tests/Integration/FormatSpec.hs b/tests/Integration/FormatSpec.hs index 1b7024236..f243da1d2 100644 --- a/tests/Integration/FormatSpec.hs +++ b/tests/Integration/FormatSpec.hs @@ -468,6 +468,18 @@ spec = do " , b = 2", "}" ] + it "formats with comments" $ + ["{{-A-}base{-B-}|{-C-}a{-D-}={-E-}1{-F-},{-G-}b{-H-}={-I-}2{-J-}}"] + `shouldFormatExpressionAs` [ "{ {- A -}", + " base", + " {- B -}", + " | {- C -}", + " a {- D -} = {- E -} 1 {- F -}", + "", + " , {- G -}", + " b {- H -} = {- I -} 2 {- J -}", + "}" + ] describe "parentheses" $ do it "removes unnecessary parentheses" $ diff --git a/tests/Parse/RecordUpdateSpec.hs b/tests/Parse/RecordUpdateSpec.hs index 5c600e945..dd31e1a40 100644 --- a/tests/Parse/RecordUpdateSpec.hs +++ b/tests/Parse/RecordUpdateSpec.hs @@ -56,7 +56,7 @@ parse str = isUpdateExpr :: Either x ((Src.Expr, [Src.Comment]), A.Position) -> Bool isUpdateExpr result = case result of - Right ((A.At _ (Src.Update _ _), _), _) -> True + Right ((A.At _ (Src.Update _ _ _), _), _) -> True _ -> False -- From e04a7f65c78cb8883d060ca1efe222c30756c2de Mon Sep 17 00:00:00 2001 From: Aaron VonderHaar Date: Mon, 28 Nov 2022 21:53:42 -0800 Subject: [PATCH 6/8] format: retain comments in record types --- compiler/src/AST/Source.hs | 5 ++- .../src/Canonicalize/Environment/Local.hs | 4 +- compiler/src/Canonicalize/Type.hs | 4 +- compiler/src/Gren/Compiler/Type.hs | 2 +- compiler/src/Gren/Format.hs | 33 ++++++++++----- compiler/src/Parse/Type.hs | 36 ++++++++-------- compiler/src/Reporting/Render/Type.hs | 4 +- tests/Integration/FormatSpec.hs | 41 +++++++++++++++++++ 8 files changed, 95 insertions(+), 34 deletions(-) diff --git a/compiler/src/AST/Source.hs b/compiler/src/AST/Source.hs index ea58b9668..4ac33d1ce 100644 --- a/compiler/src/AST/Source.hs +++ b/compiler/src/AST/Source.hs @@ -20,6 +20,7 @@ module AST.Source PArrayEntry, Type, Type_ (..), + TRecordField, SourceOrder, Module (..), getName, @@ -134,9 +135,11 @@ data Type_ | TVar Name | TType A.Region Name [([Comment], Type)] | TTypeQual A.Region Name Name [([Comment], Type)] - | TRecord [(A.Located Name, Type)] (Maybe (A.Located Name)) + | TRecord [TRecordField] (Maybe (A.Located Name)) deriving (Show) +type TRecordField = (A.Located Name, Type, SC.RecordFieldComments) + -- MODULE type SourceOrder = Int diff --git a/compiler/src/Canonicalize/Environment/Local.hs b/compiler/src/Canonicalize/Environment/Local.hs index 8efa15783..ea45976d8 100644 --- a/compiler/src/Canonicalize/Environment/Local.hs +++ b/compiler/src/Canonicalize/Environment/Local.hs @@ -136,7 +136,7 @@ getEdges edges (A.At _ tipe) = Src.TTypeQual _ _ _ args -> List.foldl' getEdges edges (fmap snd args) Src.TRecord fields _ -> - List.foldl' (\es (_, t) -> getEdges es t) edges fields + List.foldl' (\es (_, t, _) -> getEdges es t) edges fields -- CHECK FREE VARIABLES @@ -194,7 +194,7 @@ addFreeVars freeVars (A.At region tipe) = freeVars Just (A.At extRegion ext) -> Map.insert ext extRegion freeVars - in List.foldl' (\fvs (_, t) -> addFreeVars fvs t) extFreeVars fields + in List.foldl' (\fvs (_, t, _) -> addFreeVars fvs t) extFreeVars fields -- ADD CTORS diff --git a/compiler/src/Canonicalize/Type.hs b/compiler/src/Canonicalize/Type.hs index 3a99be9ce..91c9750b0 100644 --- a/compiler/src/Canonicalize/Type.hs +++ b/compiler/src/Canonicalize/Type.hs @@ -53,10 +53,10 @@ canonicalize env (A.At typeRegion tipe) = cfields <- sequenceA =<< Dups.checkFields (canonicalizeFields env fields) return $ Can.TRecord cfields (fmap A.toValue ext) -canonicalizeFields :: Env.Env -> [(A.Located Name.Name, Src.Type)] -> [(A.Located Name.Name, Result i w Can.FieldType, ())] +canonicalizeFields :: Env.Env -> [Src.TRecordField] -> [(A.Located Name.Name, Result i w Can.FieldType, ())] canonicalizeFields env fields = let len = fromIntegral (length fields) - canonicalizeField index (name, srcType) = + canonicalizeField index (name, srcType, _) = (name, Can.FieldType index <$> canonicalize env srcType, ()) in zipWith canonicalizeField [0 .. len] fields diff --git a/compiler/src/Gren/Compiler/Type.hs b/compiler/src/Gren/Compiler/Type.hs index 3ca9bf205..0190b6e8d 100644 --- a/compiler/src/Gren/Compiler/Type.hs +++ b/compiler/src/Gren/Compiler/Type.hs @@ -102,7 +102,7 @@ fromRawType (A.At _ astType) = Src.TTypeQual _ _ name args -> Type name (map (fromRawType . snd) args) Src.TRecord fields ext -> - let fromField (A.At _ field, tipe) = (field, fromRawType tipe) + let fromField (A.At _ field, tipe, _) = (field, fromRawType tipe) in Record (map fromField fields) (fmap A.toValue ext) diff --git a/compiler/src/Gren/Format.hs b/compiler/src/Gren/Format.hs index 4d4caceef..7208474fd 100644 --- a/compiler/src/Gren/Format.hs +++ b/compiler/src/Gren/Format.hs @@ -839,14 +839,24 @@ formatType = \case formatType (A.toValue arg) Src.TRecord fields Nothing -> NoTypeParens $ - group '{' ',' '}' True $ + groupWithBlankLines '{' ',' '}' True $ fmap formatField fields where - formatField (name, type_) = - spaceOrIndent - [ Block.line $ utf8 (A.toValue name) <> Block.space <> Block.char7 ':', - typeParensNone $ formatType (A.toValue type_) - ] + formatField (name, type_, SC.RecordFieldComments commentsBeforeName commentsAfterName commentsBeforeValue commentsAfterValue) = + ( if List.null commentsBeforeName then 0 else 1, + withCommentsStackBefore commentsBeforeName $ + spaceOrIndent + [ spaceOrIndent + [ withCommentsAround [] commentsAfterName $ + Block.line $ + utf8 (A.toValue name), + Block.line $ Block.char7 ':' + ], + withCommentsAround commentsBeforeValue commentsAfterValue $ + typeParensNone $ + formatType (A.toValue type_) + ] + ) Src.TRecord [] (Just base) -> NoTypeParens $ Block.line $ @@ -863,10 +873,13 @@ formatType = \case (Block.line $ utf8 $ A.toValue base) (fmap formatField $ first :| rest) where - formatField (field, type_) = - ( Nothing, - Block.line $ utf8 $ A.toValue field, - typeParensNone $ formatType $ A.toValue type_ + formatField (field, type_, SC.RecordFieldComments commentsBeforeName commentsAfterName commentsBeforeValue commentsAfterValue) = + ( formatCommentBlock commentsBeforeName, + withCommentsAround [] commentsAfterName $ Block.line $ utf8 $ A.toValue field, + withCommentsAround commentsBeforeValue commentsAfterValue $ + typeParensNone $ + formatType $ + A.toValue type_ ) data PatternBlock diff --git a/compiler/src/Parse/Type.hs b/compiler/src/Parse/Type.hs index 5eeec3445..070d5567a 100644 --- a/compiler/src/Parse/Type.hs +++ b/compiler/src/Parse/Type.hs @@ -10,6 +10,7 @@ module Parse.Type where import AST.Source qualified as Src +import AST.SourceComments qualified as SC import Data.Name qualified as Name import Parse.Primitives (Parser, addEnd, addLocation, getPosition, inContext, oneOf, oneOfWithFallback, specialize, word1, word2) import Parse.Space qualified as Space @@ -52,7 +53,7 @@ term = -- records inContext E.TRecord (word1 0x7B {- { -} E.TStart) $ do - Space.chompAndCheckIndent E.TRecordSpace E.TRecordIndentOpen + commentsAfterOpenBrace <- Space.chompAndCheckIndent E.TRecordSpace E.TRecordIndentOpen oneOf E.TRecordOpen [ do @@ -60,21 +61,25 @@ term = addEnd start (Src.TRecord [] Nothing), do name <- addLocation (Var.lower E.TRecordField) - Space.chompAndCheckIndent E.TRecordSpace E.TRecordIndentColon + commentsAfterFirstName <- Space.chompAndCheckIndent E.TRecordSpace E.TRecordIndentColon oneOf E.TRecordColon [ do word1 0x7C E.TRecordColon - Space.chompAndCheckIndent E.TRecordSpace E.TRecordIndentField - field <- chompField + commentsAfterBar <- Space.chompAndCheckIndent E.TRecordSpace E.TRecordIndentField + field <- chompField commentsAfterBar fields <- chompRecordEnd [field] + -- TODO: use commentsAfterOpenBrace + -- TODO: use commentsAfterOpenFirstName addEnd start (Src.TRecord fields (Just name)), do word1 0x3A {-:-} E.TRecordColon - Space.chompAndCheckIndent E.TRecordSpace E.TRecordIndentType + commentsAfterColon <- Space.chompAndCheckIndent E.TRecordSpace E.TRecordIndentType ((tipe, commentsAfterTipe), end) <- specialize E.TRecordType expression Space.checkIndent end E.TRecordIndentEnd - fields <- chompRecordEnd [(name, tipe)] + let fieldComments = SC.RecordFieldComments commentsAfterOpenBrace commentsAfterFirstName commentsAfterColon commentsAfterTipe + let field = (name, tipe, fieldComments) + fields <- chompRecordEnd [field] addEnd start (Src.TRecord fields Nothing) ] ] @@ -141,32 +146,31 @@ chompArgs args commentsBetween end = -- RECORD -type Field = (A.Located Name.Name, Src.Type) - -chompRecordEnd :: [Field] -> Parser E.TRecord [Field] +chompRecordEnd :: [Src.TRecordField] -> Parser E.TRecord [Src.TRecordField] chompRecordEnd fields = oneOf E.TRecordEnd [ do word1 0x2C {-,-} E.TRecordEnd - Space.chompAndCheckIndent E.TRecordSpace E.TRecordIndentField - field <- chompField + commentsAfterComma <- Space.chompAndCheckIndent E.TRecordSpace E.TRecordIndentField + field <- chompField commentsAfterComma chompRecordEnd (field : fields), do word1 0x7D {-}-} E.TRecordEnd return (reverse fields) ] -chompField :: Parser E.TRecord Field -chompField = +chompField :: [Src.Comment] -> Parser E.TRecord Src.TRecordField +chompField commentsBefore = do name <- addLocation (Var.lower E.TRecordField) - Space.chompAndCheckIndent E.TRecordSpace E.TRecordIndentColon + commentsAfterName <- Space.chompAndCheckIndent E.TRecordSpace E.TRecordIndentColon word1 0x3A {-:-} E.TRecordColon - Space.chompAndCheckIndent E.TRecordSpace E.TRecordIndentType + commentsAfterColon <- Space.chompAndCheckIndent E.TRecordSpace E.TRecordIndentType ((tipe, commentsAfterTipe), end) <- specialize E.TRecordType expression Space.checkIndent end E.TRecordIndentEnd - return (name, tipe) + let comments = SC.RecordFieldComments commentsBefore commentsAfterName commentsAfterColon commentsAfterTipe + return (name, tipe, comments) -- VARIANT diff --git a/compiler/src/Reporting/Render/Type.hs b/compiler/src/Reporting/Render/Type.hs index 910dbf804..ba3a365cf 100644 --- a/compiler/src/Reporting/Render/Type.hs +++ b/compiler/src/Reporting/Render/Type.hs @@ -129,8 +129,8 @@ srcToDoc context (A.At _ tipe) = (map srcFieldToDocs fields) (fmap (D.fromName . A.toValue) ext) -srcFieldToDocs :: (A.Located Name.Name, Src.Type) -> (Doc, Doc) -srcFieldToDocs (A.At _ fieldName, fieldType) = +srcFieldToDocs :: Src.TRecordField -> (Doc, Doc) +srcFieldToDocs (A.At _ fieldName, fieldType, _) = ( D.fromName fieldName, srcToDoc None fieldType ) diff --git a/tests/Integration/FormatSpec.hs b/tests/Integration/FormatSpec.hs index f243da1d2..fc986e777 100644 --- a/tests/Integration/FormatSpec.hs +++ b/tests/Integration/FormatSpec.hs @@ -501,6 +501,24 @@ spec = do " {}" ] + describe "types" $ do + describe "record types" $ do + it "formats with fields" $ + ["{a:Bool, b : Int}"] + `shouldFormatTypeAs` [ "{ a : Bool", + ", b : Int", + "}" + ] + it "formats comments" $ + ["{{-A-}a{-B-}:{-C-}Bool{-D-},{-E-}b{-F-}:{-G-}Int{-H-}}"] + `shouldFormatTypeAs` [ "{ {- A -}", + " a {- B -} : {- C -} Bool {- D -}", + "", + ", {- E -}", + " b {- F -} : {- G -} Int {- H -}", + "}" + ] + assertFormatted :: [Text] -> IO () assertFormatted lines_ = lines_ `shouldFormatAs` lines_ @@ -559,3 +577,26 @@ shouldFormatExpressionAs inputLines expectedOutputLines = if text == "" then Just text else LazyText.stripPrefix " " text + +shouldFormatTypeAs :: [Text] -> [Text] -> IO () +shouldFormatTypeAs inputLines expectedOutputLines = + let input = TE.encodeUtf8 $ "type alias Type =\n" <> Text.unlines (fmap (" " <>) inputLines) + expectedOutput = LazyText.unlines $ fmap LazyText.fromStrict expectedOutputLines + actualOutput = LTE.decodeUtf8 . Builder.toLazyByteString <$> Format.formatByteString Parse.Application input + cleanOutput i = + LazyText.stripPrefix "module Main exposing (..)\n\n\n\ntype alias Type =\n" i + >>= (return . LazyText.lines) + >>= traverse stripIndent + >>= (return . LazyText.unlines) + in case fmap cleanOutput actualOutput of + Left err -> + expectationFailure ("shouldFormatTypeAs: failed to format: " <> show err) + Right Nothing -> + expectationFailure ("shouldFormatTypeAs: internal error: couldn't clean output: " <> show actualOutput) + Right (Just actualExpression) -> + actualExpression `shouldBe` expectedOutput + where + stripIndent text = + if text == "" + then Just text + else LazyText.stripPrefix " " text From 8ae297a6fcaff85a4b1d358634d4ac56716c62ff Mon Sep 17 00:00:00 2001 From: Aaron VonderHaar Date: Mon, 28 Nov 2022 22:02:07 -0800 Subject: [PATCH 7/8] format: retain comments in record extension types --- compiler/src/AST/Source.hs | 2 +- .../src/Canonicalize/Environment/Local.hs | 2 +- compiler/src/Canonicalize/Type.hs | 2 +- compiler/src/Gren/Compiler/Type.hs | 2 +- compiler/src/Gren/Format.hs | 6 +++--- compiler/src/Parse/Type.hs | 5 ++--- compiler/src/Reporting/Render/Type.hs | 2 +- tests/Integration/FormatSpec.hs | 20 +++++++++++++++++++ 8 files changed, 30 insertions(+), 11 deletions(-) diff --git a/compiler/src/AST/Source.hs b/compiler/src/AST/Source.hs index 4ac33d1ce..c5152af17 100644 --- a/compiler/src/AST/Source.hs +++ b/compiler/src/AST/Source.hs @@ -135,7 +135,7 @@ data Type_ | TVar Name | TType A.Region Name [([Comment], Type)] | TTypeQual A.Region Name Name [([Comment], Type)] - | TRecord [TRecordField] (Maybe (A.Located Name)) + | TRecord [TRecordField] (Maybe (A.Located Name, SC.UpdateComments)) deriving (Show) type TRecordField = (A.Located Name, Type, SC.RecordFieldComments) diff --git a/compiler/src/Canonicalize/Environment/Local.hs b/compiler/src/Canonicalize/Environment/Local.hs index ea45976d8..18b560328 100644 --- a/compiler/src/Canonicalize/Environment/Local.hs +++ b/compiler/src/Canonicalize/Environment/Local.hs @@ -192,7 +192,7 @@ addFreeVars freeVars (A.At region tipe) = case maybeExt of Nothing -> freeVars - Just (A.At extRegion ext) -> + Just (A.At extRegion ext, _) -> Map.insert ext extRegion freeVars in List.foldl' (\fvs (_, t, _) -> addFreeVars fvs t) extFreeVars fields diff --git a/compiler/src/Canonicalize/Type.hs b/compiler/src/Canonicalize/Type.hs index 91c9750b0..fc734f512 100644 --- a/compiler/src/Canonicalize/Type.hs +++ b/compiler/src/Canonicalize/Type.hs @@ -51,7 +51,7 @@ canonicalize env (A.At typeRegion tipe) = Src.TRecord fields ext -> do cfields <- sequenceA =<< Dups.checkFields (canonicalizeFields env fields) - return $ Can.TRecord cfields (fmap A.toValue ext) + return $ Can.TRecord cfields (fmap (A.toValue . fst) ext) canonicalizeFields :: Env.Env -> [Src.TRecordField] -> [(A.Located Name.Name, Result i w Can.FieldType, ())] canonicalizeFields env fields = diff --git a/compiler/src/Gren/Compiler/Type.hs b/compiler/src/Gren/Compiler/Type.hs index 0190b6e8d..eb67b6406 100644 --- a/compiler/src/Gren/Compiler/Type.hs +++ b/compiler/src/Gren/Compiler/Type.hs @@ -105,7 +105,7 @@ fromRawType (A.At _ astType) = let fromField (A.At _ field, tipe, _) = (field, fromRawType tipe) in Record (map fromField fields) - (fmap A.toValue ext) + (fmap (A.toValue . fst) ext) -- JSON for PROGRAM diff --git a/compiler/src/Gren/Format.hs b/compiler/src/Gren/Format.hs index 7208474fd..b0a6dfe66 100644 --- a/compiler/src/Gren/Format.hs +++ b/compiler/src/Gren/Format.hs @@ -857,12 +857,12 @@ formatType = \case formatType (A.toValue type_) ] ) - Src.TRecord [] (Just base) -> + Src.TRecord [] (Just (base, _)) -> NoTypeParens $ Block.line $ utf8 $ A.toValue base - Src.TRecord (first : rest) (Just base) -> + Src.TRecord (first : rest) (Just (base, SC.UpdateComments commentsBeforeBase commentsAfterBase)) -> NoTypeParens $ extendedGroup '{' @@ -870,7 +870,7 @@ formatType = \case ',' ':' '}' - (Block.line $ utf8 $ A.toValue base) + (withCommentsStackAround commentsBeforeBase commentsAfterBase $ Block.line $ utf8 $ A.toValue base) (fmap formatField $ first :| rest) where formatField (field, type_, SC.RecordFieldComments commentsBeforeName commentsAfterName commentsBeforeValue commentsAfterValue) = diff --git a/compiler/src/Parse/Type.hs b/compiler/src/Parse/Type.hs index 070d5567a..0ae7e398d 100644 --- a/compiler/src/Parse/Type.hs +++ b/compiler/src/Parse/Type.hs @@ -67,11 +67,10 @@ term = [ do word1 0x7C E.TRecordColon commentsAfterBar <- Space.chompAndCheckIndent E.TRecordSpace E.TRecordIndentField + let baseComments = SC.UpdateComments commentsAfterOpenBrace commentsAfterFirstName field <- chompField commentsAfterBar fields <- chompRecordEnd [field] - -- TODO: use commentsAfterOpenBrace - -- TODO: use commentsAfterOpenFirstName - addEnd start (Src.TRecord fields (Just name)), + addEnd start (Src.TRecord fields (Just (name, baseComments))), do word1 0x3A {-:-} E.TRecordColon commentsAfterColon <- Space.chompAndCheckIndent E.TRecordSpace E.TRecordIndentType diff --git a/compiler/src/Reporting/Render/Type.hs b/compiler/src/Reporting/Render/Type.hs index ba3a365cf..57e82946a 100644 --- a/compiler/src/Reporting/Render/Type.hs +++ b/compiler/src/Reporting/Render/Type.hs @@ -127,7 +127,7 @@ srcToDoc context (A.At _ tipe) = Src.TRecord fields ext -> record (map srcFieldToDocs fields) - (fmap (D.fromName . A.toValue) ext) + (fmap (D.fromName . A.toValue . fst) ext) srcFieldToDocs :: Src.TRecordField -> (Doc, Doc) srcFieldToDocs (A.At _ fieldName, fieldType, _) = diff --git a/tests/Integration/FormatSpec.hs b/tests/Integration/FormatSpec.hs index fc986e777..0a4ad4237 100644 --- a/tests/Integration/FormatSpec.hs +++ b/tests/Integration/FormatSpec.hs @@ -518,6 +518,26 @@ spec = do " b {- F -} : {- G -} Int {- H -}", "}" ] + describe "record extension types" $ do + it "formats" $ + ["{base|a:Bool,b:Int}"] + `shouldFormatTypeAs` [ "{ base", + " | a : Bool", + " , b : Int", + "}" + ] + it "formats with comments" $ + ["{{-A-}base{-B-}|{-C-}a{-D-}:{-E-}Bool{-F-},{-G-}b{-H-}:{-I-}Int{-J-}}"] + `shouldFormatTypeAs` [ "{ {- A -}", + " base", + " {- B -}", + " | {- C -}", + " a {- D -} : {- E -} Bool {- F -}", + "", + " , {- G -}", + " b {- H -} : {- I -} Int {- J -}", + "}" + ] assertFormatted :: [Text] -> IO () assertFormatted lines_ = From 2d5c032de638f588bd14b5a8aacd92e77d81397a Mon Sep 17 00:00:00 2001 From: Aaron VonderHaar Date: Mon, 28 Nov 2022 22:21:56 -0800 Subject: [PATCH 8/8] format: fix regressions for record updates with multiline field values --- compiler/src/Gren/Format.hs | 6 ++++-- tests/Integration/FormatSpec.hs | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/compiler/src/Gren/Format.hs b/compiler/src/Gren/Format.hs index b0a6dfe66..0eb1ff320 100644 --- a/compiler/src/Gren/Format.hs +++ b/compiler/src/Gren/Format.hs @@ -136,8 +136,10 @@ extendedGroup open baseSep sep fieldSep close base fields = NonEmpty.prependList (maybeToList before) $ NonEmpty.singleton $ spaceOrIndent - [ key, - Block.line $ Block.char7 fieldSep, + [ spaceOrIndent + [ key, + Block.line $ Block.char7 fieldSep + ], value ] diff --git a/tests/Integration/FormatSpec.hs b/tests/Integration/FormatSpec.hs index 0a4ad4237..fc4a49d2c 100644 --- a/tests/Integration/FormatSpec.hs +++ b/tests/Integration/FormatSpec.hs @@ -451,6 +451,15 @@ spec = do ", b = 2", "}" ] + it "formats field with multiline value" $ + [ "{a = --X", + "1}" + ] + `shouldFormatExpressionAs` [ "{ a =", + " -- X", + " 1", + "}" + ] it "formats comments" $ ["{{-A-}a{-B-}={-C-}1{-D-},{-E-}b{-F-}={-G-}2{-H-}}"] `shouldFormatExpressionAs` [ "{ {- A -}", @@ -468,6 +477,16 @@ spec = do " , b = 2", "}" ] + it "formats field with multiline value" $ + [ "{base|a = --X", + "1}" + ] + `shouldFormatExpressionAs` [ "{ base", + " | a =", + " -- X", + " 1", + "}" + ] it "formats with comments" $ ["{{-A-}base{-B-}|{-C-}a{-D-}={-E-}1{-F-},{-G-}b{-H-}={-I-}2{-J-}}"] `shouldFormatExpressionAs` [ "{ {- A -}",