Skip to content

Commit 140c3ad

Browse files
committed
imp:lots: show precise posting location in unclassified lotful error
Use index-based posting lookup (makePostingErrorExcerptByIndex) instead of equality-based search, which failed when postings were modified by the balancer after parsing. The error now points to the specific posting line rather than the transaction start.
1 parent 8fdcedd commit 140c3ad

File tree

2 files changed

+30
-9
lines changed

2 files changed

+30
-9
lines changed

hledger-lib/Hledger/Data/Errors.hs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module Hledger.Data.Errors (
1111
makePriceDirectiveErrorExcerpt,
1212
makeTransactionErrorExcerpt,
1313
makePostingErrorExcerpt,
14+
makePostingErrorExcerptByIndex,
1415
makePostingAccountErrorExcerpt,
1516
makeBalanceAssertionErrorExcerpt,
1617
transactionFindPostingIndex,
@@ -199,6 +200,24 @@ decoratePostingErrorExcerpt absline relline mcols txt =
199200
lineprefix = T.replicate marginw " " <> "| "
200201
where marginw = length (show absline) + 1
201202

203+
-- | Like 'makePostingErrorExcerpt', but identifies the posting by its
204+
-- 0-based index in the transaction rather than by equality search.
205+
-- This avoids false mismatches when postings have been modified after parsing
206+
-- (e.g. by the balancer), and is unambiguous when duplicate postings exist.
207+
makePostingErrorExcerptByIndex :: Transaction -> Int -> (FilePath, Int, Maybe (Int, Maybe Int), Text)
208+
makePostingErrorExcerptByIndex t idx = (f, errabsline, Nothing, ex)
209+
where
210+
(SourcePos f tl _) = fst $ tsourcepos t
211+
errrelline =
212+
commentExtraLines (tcomment t) +
213+
sum (map postingLines $ take (idx + 1) $ tpostings t)
214+
where
215+
postingLines p' = 1 + commentExtraLines (pcomment p')
216+
commentExtraLines c = max 0 (length (T.lines c) - 1)
217+
errabsline = unPos tl + errrelline
218+
txntxt = showTransaction t & textChomp & (<>"\n")
219+
ex = decoratePostingErrorExcerpt errabsline errrelline Nothing txntxt
220+
202221
-- | Find the 1-based index of the first posting in this transaction
203222
-- satisfying the given predicate.
204223
transactionFindPostingIndex :: (Posting -> Bool) -> Transaction -> Maybe Int

hledger-lib/Hledger/Data/Lots.hs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ import Hledger.Data.AccountName (accountNameType)
125125
import Hledger.Data.Dates (showDate)
126126
import Hledger.Data.AccountType (isAssetType, isEquityType)
127127
import Hledger.Data.Amount (amountSetQuantity, amountsRaw, isNegativeAmount, maNegate, mapMixedAmount, mixedAmount, mixedAmountLooksZero, nullmixedamt, noCostFmt, showAmountWith, showMixedAmountOneLine)
128-
import Hledger.Data.Errors (makePostingErrorExcerpt, makeTransactionErrorExcerpt)
128+
import Hledger.Data.Errors (makePostingErrorExcerpt, makePostingErrorExcerptByIndex, makeTransactionErrorExcerpt)
129129
import Hledger.Data.Journal (journalAccountType, journalCommodityLotsMethod, journalCommodityUsesLots, journalFilePaths, journalInheritedAccountTags, journalMapTransactions, journalTieTransactions, parseReductionMethod)
130130
import Hledger.Data.Posting (generatedPostingTagName, hasAmount, isReal, nullposting, originalPosting, postingAddHiddenAndMaybeVisibleTag, postingStripCosts)
131131
import Hledger.Data.Transaction (txnTieKnot)
@@ -590,7 +590,7 @@ transactionClassifyLotPostings verbosetags lookupAccountType commodityIsLotful t
590590
journalCalculateLots :: Bool -> Journal -> Either String Journal
591591
journalCalculateLots verbosetags j
592592
| not $ any (any isLotPosting . tpostings) txns = do
593-
mapM_ checkUnclassified [p | t <- txns, p <- tpostings t]
593+
mapM_ checkUnclassified [(t, i, p) | t <- txns, (i, p) <- zip [0..] (tpostings t)]
594594
Right j
595595
| otherwise = do
596596
validateUserLabels txns
@@ -599,8 +599,8 @@ journalCalculateLots verbosetags j
599599
Right (journalTieTransactions $ j{jtxns = reverse txns'})
600600
where
601601
txns = jtxns j
602-
checkUnclassified p
603-
| isUnclassifiedLotfulPosting j p = Left (unclassifiedLotWarning j p)
602+
checkUnclassified (t, i, p)
603+
| isUnclassifiedLotfulPosting j p = Left (unclassifiedLotWarning j t i p)
604604
| otherwise = Right ()
605605

606606
-- Disposal balancing
@@ -770,17 +770,19 @@ isUnclassifiedLotfulPosting j p =
770770
| hasAccountLotsTag = any ((/= 0) . aquantity) amts
771771
| otherwise = False
772772

773-
-- | Build a warning message for an unclassified lotful posting.
774-
unclassifiedLotWarning :: Journal -> Posting -> String
775-
unclassifiedLotWarning j p =
773+
-- | Build an error message for an unclassified lotful posting.
774+
-- Takes the transaction and the 0-based posting index for precise source location.
775+
unclassifiedLotWarning :: Journal -> Transaction -> Int -> Posting -> String
776+
unclassifiedLotWarning j t idx p =
776777
let amts = amountsRaw (pamount p)
777778
lotfulCommodities = [acommodity a | a <- amts, journalCommodityUsesLots j (acommodity a)]
778779
hasAccountTag = any ((== "lots") . T.toLower . fst) (ptags p)
779780
source = case (lotfulCommodities, hasAccountTag) of
780781
(c:_, _) -> T.unpack c ++ " is declared lotful (commodity lots: tag)"
781782
([], True) -> T.unpack (paccount p) ++ " is declared lotful (account lots: tag)"
782783
_ -> "posting involves a lotful commodity or account"
783-
in postingErrPrefix p
784+
(f, line, _, ex) = makePostingErrorExcerptByIndex t idx
785+
in printf "%s:%d:\n%s\n" f line ex
784786
++ source ++ " but this posting was not classified as\n"
785787
++ "acquire, dispose, or transfer. Lot state will not be updated.\n"
786788
++ "Possible fixes: add a cost basis ({$X}), a price (@ $X),\n"
@@ -937,7 +939,7 @@ processTransaction verbosetags j needsLabels (ls, acc) t = do
937939
(st', newPs) <- processDisposePosting verbosetags j t st p
938940
foldMPostings st' (reverse newPs ++ acc') ps tmap
939941
| isUnclassifiedLotfulPosting j p =
940-
Left (unclassifiedLotWarning j p)
942+
Left (unclassifiedLotWarning j t i p)
941943
| otherwise =
942944
foldMPostings st (p:acc') ps tmap
943945

0 commit comments

Comments
 (0)