Skip to content

Commit c6aba87

Browse files
lf-parsonsmatt
andauthored
Add position info to EntityDef (#1569)
* update nix deps * Add position info to EntityDef This is an initial implementation that we can API-compatibly improve in the future by providing more accurate information. However, to do that, we would have to rewrite the entity definition parser with e.g. megaparsec. This is a reasonable course of action but rewriting it is going to be more work and we can ship position info we could improve later with the existing parser. * Version 2.15.0.0 * changelog * move: Database.Persist.TH internals -> Database.Persist.TH.Internal It winds up making a lot more sense legibility wise to just move the entire module. Maybe the API could be shifted back later (but it uses a pile of internals that really are probably more bothersome to have in a different file?), but this is at least easiest to review since it is quite literally just copy pasting the entire content of the module. * fix compilation error * fix compilation --------- Co-authored-by: parsonsmatt <parsonsmatt@gmail.com>
1 parent 1c75fed commit c6aba87

File tree

16 files changed

+3644
-3432
lines changed

16 files changed

+3644
-3432
lines changed

flake.lock

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
flake-utils.url = "github:numtide/flake-utils";
99
};
1010

11+
1112
outputs = { self, nixpkgs, flake-utils, haskellNix }:
1213
flake-utils.lib.eachDefaultSystem (system:
1314
let

persistent-test/persistent-test.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ library
7676
, monad-logger >= 0.3.25
7777
, mtl
7878
, path-pieces >= 0.2
79-
, persistent >= 2.14 && < 2.15
79+
, persistent >= 2.14 && < 2.16
8080
, QuickCheck >= 2.9
8181
, quickcheck-instances >= 0.3
8282
, random >= 1.1

persistent/ChangeLog.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog for persistent
22

3+
# 2.15.0.0
4+
5+
* [#1569](https://github.com/yesodweb/persistent/pull/1569)
6+
* Add position information to `EntityDef`, `UnboundEntityDef` via a `Span`
7+
field as a preliminary design that just gives the entire span of the
8+
input text (i.e. the entire file or quasiquote the item is defined in).
9+
* Move `Database.Persist.TH` internals to `Database.Persist.TH.Internal` and
10+
no longer export the following internals:
11+
* lensPTH
12+
* parseReferences
13+
* embedEntityDefs
14+
* fieldError
15+
* AtLeastOneUniqueKey(..)
16+
* OnlyOneUniqueKey(..)
17+
* pkNewtype
18+
319
## 2.14.6.3
420

521
* [#1544](https://github.com/yesodweb/persistent/pull/1544)

persistent/Database/Persist/EntityDef.hs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module Database.Persist.EntityDef
1919
, getEntityKeyFields
2020
, getEntityComments
2121
, getEntityExtra
22+
, getEntitySpan
2223
, isEntitySum
2324
, entityPrimary
2425
, entitiesPrimary
@@ -40,7 +41,8 @@ import Database.Persist.EntityDef.Internal
4041
import Database.Persist.FieldDef
4142

4243
import Database.Persist.Names
43-
import Database.Persist.Types.Base (ForeignDef, UniqueDef(..), entityKeyFields)
44+
import Database.Persist.Types.Base
45+
(ForeignDef, Span, UniqueDef(..), entityKeyFields)
4446

4547
-- | Retrieve the list of 'UniqueDef' from an 'EntityDef'. This does not include
4648
-- a @Primary@ key, if one is defined. A future version of @persistent@ will
@@ -205,3 +207,13 @@ overEntityFields
205207
-> EntityDef
206208
overEntityFields f ed =
207209
setEntityFields (f (getEntityFieldsDatabase ed)) ed
210+
211+
-- | Gets the 'Span' of the definition of the entity.
212+
--
213+
-- Note that as of this writing the span covers the entire file or quasiquote
214+
-- where the item is defined due to parsing limitations. This may be changed in
215+
-- a future release to be more accurate.
216+
--
217+
-- @since 2.15.0.0
218+
getEntitySpan :: EntityDef -> Maybe Span
219+
getEntitySpan = entitySpan

persistent/Database/Persist/Quasi/Internal.hs

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
{-# LANGUAGE RecordWildCards #-}
66
{-# LANGUAGE StandaloneDeriving #-}
77
{-# LANGUAGE StrictData #-}
8+
{-# LANGUAGE TupleSections #-}
89
{-# LANGUAGE UndecidableInstances #-}
910
{-# LANGUAGE ViewPatterns #-}
1011

@@ -21,6 +22,8 @@ module Database.Persist.Quasi.Internal
2122
, toFKNameInfixed
2223
, Token (..)
2324
, Line (..)
25+
, SourceLoc(..)
26+
, sourceLocFromTHLoc
2427
, preparse
2528
, parseLine
2629
, parseFieldType
@@ -66,7 +69,7 @@ import qualified Data.Text as T
6669
import Database.Persist.EntityDef.Internal
6770
import Database.Persist.Types
6871
import Database.Persist.Types.Base
69-
import Language.Haskell.TH.Syntax (Lift)
72+
import Language.Haskell.TH.Syntax (Lift, Loc(..))
7073
import qualified Text.Read as R
7174

7275
data ParseState a = PSDone | PSFail String | PSSuccess a Text deriving Show
@@ -205,14 +208,48 @@ toFKNameInfixed :: Text -> EntityNameHS -> ConstraintNameHS -> Text
205208
toFKNameInfixed inf (EntityNameHS entName) (ConstraintNameHS conName) =
206209
entName <> inf <> conName
207210

211+
-- | Source location: file and line/col information. This is half of a 'Span'.
212+
data SourceLoc = SourceLoc
213+
{ locFile :: Text
214+
, locStartLine :: Int
215+
, locStartCol :: Int
216+
} deriving (Show, Lift)
217+
218+
sourceLocFromTHLoc :: Loc -> SourceLoc
219+
sourceLocFromTHLoc Loc {loc_filename=filename, loc_start=start} =
220+
SourceLoc {locFile = T.pack filename, locStartLine = fst start, locStartCol = snd start}
221+
222+
208223
-- | Parses a quasi-quoted syntax into a list of entity definitions.
209-
parse :: PersistSettings -> Text -> [UnboundEntityDef]
210-
parse ps = maybe [] (parseLines ps) . preparse
224+
parse :: PersistSettings -> [(Maybe SourceLoc, Text)] -> [UnboundEntityDef]
225+
parse ps blocks =
226+
mconcat $ handleBlock <$> blocks
227+
where
228+
handleBlock (mLoc, block) =
229+
maybe []
230+
(\(numLines, lns) -> parseLines ps (approximateSpan numLines block <$> mLoc) lns)
231+
(preparse block)
232+
-- FIXME: put an actually truthful span into here
233+
-- We can't give a better result if we push any of this down into the
234+
-- parser at the moment since the parser throws out location info by
235+
-- e.g. discarding comment lines completely without keeping track of
236+
-- where it is. The realistic fix to this is rewriting the parser in
237+
-- Megaparsec, which is a reasonable idea that should actually be done.
238+
approximateSpan numLines block loc =
239+
Span
240+
{ spanFile = locFile loc
241+
, spanStartLine = locStartLine loc
242+
, spanStartCol = locStartCol loc
243+
, spanEndLine = locStartLine loc + numLines - 1
244+
-- Last line's length plus one (since we are one-past-the-end)
245+
, spanEndCol = (+ 1) . T.length . T.takeWhileEnd (/= '\n') $ block
246+
}
211247

212-
preparse :: Text -> Maybe (NonEmpty Line)
248+
preparse :: Text -> Maybe (Int, NonEmpty Line)
213249
preparse txt = do
214250
lns <- NEL.nonEmpty (T.lines txt)
215-
NEL.nonEmpty $ mapMaybe parseLine (NEL.toList lns)
251+
let rawLineCount = length lns
252+
(rawLineCount,) <$> NEL.nonEmpty (mapMaybe parseLine (NEL.toList lns))
216253

217254
parseLine :: Text -> Maybe Line
218255
parseLine txt = do
@@ -303,9 +340,9 @@ lowestIndent :: NonEmpty Line -> Int
303340
lowestIndent = minimum . fmap lineIndent
304341

305342
-- | Divide lines into blocks and make entity definitions.
306-
parseLines :: PersistSettings -> NonEmpty Line -> [UnboundEntityDef]
307-
parseLines ps = do
308-
fmap (mkUnboundEntityDef ps . toParsedEntityDef) . associateLines
343+
parseLines :: PersistSettings -> Maybe Span -> NonEmpty Line -> [UnboundEntityDef]
344+
parseLines ps mSpan = do
345+
fmap (mkUnboundEntityDef ps . toParsedEntityDef mSpan) . associateLines
309346

310347
data ParsedEntityDef = ParsedEntityDef
311348
{ parsedEntityDefComments :: [Text]
@@ -314,6 +351,7 @@ data ParsedEntityDef = ParsedEntityDef
314351
, parsedEntityDefEntityAttributes :: [Attr]
315352
, parsedEntityDefFieldAttributes :: [[Token]]
316353
, parsedEntityDefExtras :: M.Map Text [ExtraLine]
354+
, parsedEntityDefSpan :: Maybe Span
317355
}
318356

319357
entityNamesFromParsedDef :: PersistSettings -> ParsedEntityDef -> (EntityNameHS, EntityNameDB)
@@ -325,14 +363,15 @@ entityNamesFromParsedDef ps parsedEntDef = (entNameHS, entNameDB)
325363
entNameDB =
326364
EntityNameDB $ getDbName ps (unEntityNameHS entNameHS) (parsedEntityDefEntityAttributes parsedEntDef)
327365

328-
toParsedEntityDef :: LinesWithComments -> ParsedEntityDef
329-
toParsedEntityDef lwc = ParsedEntityDef
366+
toParsedEntityDef :: Maybe Span -> LinesWithComments -> ParsedEntityDef
367+
toParsedEntityDef mSpan lwc = ParsedEntityDef
330368
{ parsedEntityDefComments = lwcComments lwc
331369
, parsedEntityDefEntityName = entNameHS
332370
, parsedEntityDefIsSum = isSum
333371
, parsedEntityDefEntityAttributes = entAttribs
334372
, parsedEntityDefFieldAttributes = attribs
335373
, parsedEntityDefExtras = extras
374+
, parsedEntityDefSpan = mSpan
336375
}
337376
where
338377
entityLine :| fieldLines =
@@ -458,6 +497,10 @@ data UnboundEntityDef
458497
-- the field?" yet, so we defer those to the Template Haskell execution.
459498
--
460499
-- @since 2.13.0.0
500+
, unboundEntityDefSpan :: Maybe Span
501+
-- ^ The source code span of this entity in the models file.
502+
--
503+
-- @since 2.15.0.0
461504
}
462505
deriving (Eq, Ord, Show, Lift)
463506

@@ -481,6 +524,7 @@ unbindEntityDef ed =
481524
ed
482525
, unboundEntityFields =
483526
map unbindFieldDef (entityFields ed)
527+
, unboundEntityDefSpan = entitySpan ed
484528
}
485529

486530
-- | Returns the @['UnboundFieldDef']@ for an 'UnboundEntityDef'. This returns
@@ -689,6 +733,7 @@ mkUnboundEntityDef ps parsedEntDef =
689733
DefaultKey (FieldNameDB $ psIdName ps)
690734
, unboundEntityFields =
691735
cols
736+
, unboundEntityDefSpan = parsedEntityDefSpan parsedEntDef
692737
, unboundEntityDef =
693738
EntityDef
694739
{ entityHaskell = entNameHS
@@ -712,6 +757,7 @@ mkUnboundEntityDef ps parsedEntDef =
712757
case parsedEntityDefComments parsedEntDef of
713758
[] -> Nothing
714759
comments -> Just (T.unlines comments)
760+
, entitySpan = parsedEntityDefSpan parsedEntDef
715761
}
716762
}
717763
where

0 commit comments

Comments
 (0)