Simon Jakobi pushed to branch wip/sjakobi/T23414 at Glasgow Haskell Compiler / GHC
Commits:
-
f011f329
by Simon Jakobi at 2026-05-22T23:20:42+02:00
-
75d0d232
by Simon Jakobi at 2026-05-23T14:58:44+02:00
-
c65d3550
by Simon Jakobi at 2026-05-23T15:07:49+02:00
12 changed files:
- compiler/GHC/Driver/Errors.hs
- compiler/GHC/Driver/Errors/Ppr.hs
- compiler/GHC/Rename/Utils.hs
- compiler/GHC/Tc/Errors/Ppr.hs
- compiler/GHC/Types/Error.hs
- compiler/GHC/Utils/Logger.hs
- testsuite/tests/patsyn/should_fail/T14114.stderr
- testsuite/tests/patsyn/should_fail/all.T
- testsuite/tests/rename/should_fail/all.T
- testsuite/tests/rename/should_fail/rnfail004.stderr
- testsuite/tests/typecheck/should_fail/all.T
- testsuite/tests/typecheck/should_fail/tcfail038.stderr
Changes:
| ... | ... | @@ -15,6 +15,7 @@ import GHC.Utils.Json |
| 15 | 15 | import GHC.Utils.Error
|
| 16 | 16 | import GHC.Utils.Outputable
|
| 17 | 17 | import GHC.Utils.Logger
|
| 18 | +import Data.List.NonEmpty (NonEmpty(..))
|
|
| 18 | 19 | |
| 19 | 20 | reportError :: Logger -> NamePprCtx -> DiagOpts -> SrcSpan -> SDoc -> IO ()
|
| 20 | 21 | reportError logger nameContext opts span doc = do
|
| ... | ... | @@ -47,7 +48,7 @@ printMessages logger msg_opts opts = mapM_ (printMessage logger msg_opts opts) . |
| 47 | 48 | printMessage :: forall a. (Diagnostic a) => Logger -> DiagnosticOpts a -> DiagOpts -> MsgEnvelope a -> IO ()
|
| 48 | 49 | printMessage logger msg_opts opts message
|
| 49 | 50 | | log_diags_as_json = do
|
| 50 | - decorated <- decorateDiagnostic logflags messageClass location doc
|
|
| 51 | + decorated <- decorateDiagnostic logflags messageClass location sourceSpans doc
|
|
| 51 | 52 | let
|
| 52 | 53 | rendered :: String
|
| 53 | 54 | rendered = renderWithContext (log_default_user_context logflags) decorated
|
| ... | ... | @@ -57,7 +58,7 @@ printMessage logger msg_opts opts message |
| 57 | 58 | |
| 58 | 59 | logJsonMsg logger messageClass jsonMessage
|
| 59 | 60 | |
| 60 | - | otherwise = logMsg logger messageClass location doc
|
|
| 61 | + | otherwise = logMsg (pushLogHook renderWithSourceSpans logger) messageClass location doc
|
|
| 61 | 62 | where
|
| 62 | 63 | logflags :: LogFlags
|
| 63 | 64 | logflags = logFlags logger
|
| ... | ... | @@ -80,9 +81,21 @@ printMessage logger msg_opts opts message |
| 80 | 81 | diagnostic :: a
|
| 81 | 82 | diagnostic = errMsgDiagnostic message
|
| 82 | 83 | |
| 84 | + sourceSpans :: NonEmpty SrcSpan
|
|
| 85 | + sourceSpans = location :| diagnosticRelatedLocations diagnostic
|
|
| 86 | + |
|
| 83 | 87 | severity :: Severity
|
| 84 | 88 | severity = errMsgSeverity message
|
| 85 | 89 | |
| 90 | + renderWithSourceSpans :: LogAction -> LogAction
|
|
| 91 | + renderWithSourceSpans fallback logflags' msg_class' srcSpan' msg' =
|
|
| 92 | + case msg_class' of
|
|
| 93 | + MCDiagnostic _ _ _ -> do
|
|
| 94 | + decorated <- decorateDiagnostic logflags' msg_class' srcSpan' sourceSpans msg'
|
|
| 95 | + fallback logflags' MCInfo noSrcSpan decorated
|
|
| 96 | + _ ->
|
|
| 97 | + fallback logflags' msg_class' srcSpan' msg'
|
|
| 98 | + |
|
| 86 | 99 | messageWithHints :: a -> SDoc
|
| 87 | 100 | messageWithHints e =
|
| 88 | 101 | let main_msg = formatBulleted $ diagnosticMessage msg_opts e
|
| ... | ... | @@ -87,6 +87,18 @@ instance Diagnostic GhcMessage where |
| 87 | 87 | |
| 88 | 88 | diagnosticCode = constructorCode @GHC
|
| 89 | 89 | |
| 90 | + diagnosticRelatedLocations = \case
|
|
| 91 | + GhcPsMessage m
|
|
| 92 | + -> diagnosticRelatedLocations m
|
|
| 93 | + GhcTcRnMessage m
|
|
| 94 | + -> diagnosticRelatedLocations m
|
|
| 95 | + GhcDsMessage m
|
|
| 96 | + -> diagnosticRelatedLocations m
|
|
| 97 | + GhcDriverMessage m
|
|
| 98 | + -> diagnosticRelatedLocations m
|
|
| 99 | + GhcUnknownMessage m
|
|
| 100 | + -> diagnosticRelatedLocations m
|
|
| 101 | + |
|
| 90 | 102 | instance HasDefaultDiagnosticOpts DriverMessageOpts where
|
| 91 | 103 | defaultOpts = DriverMessageOpts (defaultDiagnosticOpts @PsMessage) (defaultDiagnosticOpts @IfaceMessage)
|
| 92 | 104 |
| ... | ... | @@ -660,9 +660,10 @@ mkNameClashErr gre_env rdr_name gres = TcRnAmbiguousName gre_env rdr_name gres |
| 660 | 660 | |
| 661 | 661 | dupNamesErr :: NE.NonEmpty SrcSpan -> NE.NonEmpty RdrName -> RnM ()
|
| 662 | 662 | dupNamesErr locs names
|
| 663 | - = addErrAt big_loc (TcRnBindingNameConflict (NE.head names) locs)
|
|
| 663 | + = addErrAt (NE.head sorted_locs)
|
|
| 664 | + (TcRnBindingNameConflict (NE.head names) sorted_locs)
|
|
| 664 | 665 | where
|
| 665 | - big_loc = foldr1 combineSrcSpans locs
|
|
| 666 | + sorted_locs = NE.sortBy leftmost_smallest locs
|
|
| 666 | 667 | |
| 667 | 668 | badQualBndrErr :: RdrName -> TcRnMessage
|
| 668 | 669 | badQualBndrErr rdr_name = TcRnQualifiedBinder rdr_name
|
| ... | ... | @@ -3455,6 +3455,18 @@ instance Diagnostic TcRnMessage where |
| 3455 | 3455 | |
| 3456 | 3456 | diagnosticCode = constructorCode @GHC
|
| 3457 | 3457 | |
| 3458 | + diagnosticRelatedLocations = \case
|
|
| 3459 | + TcRnUnknownMessage m
|
|
| 3460 | + -> diagnosticRelatedLocations m
|
|
| 3461 | + TcRnMessageWithInfo _ (TcRnMessageDetailed _ msg')
|
|
| 3462 | + -> diagnosticRelatedLocations msg'
|
|
| 3463 | + TcRnWithHsDocContext _ msg'
|
|
| 3464 | + -> diagnosticRelatedLocations msg'
|
|
| 3465 | + TcRnBindingNameConflict _ locs
|
|
| 3466 | + -> NE.tail locs
|
|
| 3467 | + _ ->
|
|
| 3468 | + []
|
|
| 3469 | + |
|
| 3458 | 3470 | pprTcRnBadlyLevelled :: LevelCheckReason -> Set.Set ThLevelIndex -> ThLevelIndex -> Maybe ErrorItem -> DecoratedSDoc
|
| 3459 | 3471 | pprTcRnBadlyLevelled reason bind_lvls use_lvl lift_attempt = mkDecorated $
|
| 3460 | 3472 | [ fsep [ text "Level error:", pprLevelCheckReason reason
|
| ... | ... | @@ -66,6 +66,7 @@ module GHC.Types.Error |
| 66 | 66 | , mkLocMessageWarningGroups
|
| 67 | 67 | , formatDiagnostic
|
| 68 | 68 | , getCaretDiagnostic
|
| 69 | + , getCaretDiagnostics
|
|
| 69 | 70 | |
| 70 | 71 | , jsonDiagnostic
|
| 71 | 72 | |
| ... | ... | @@ -108,8 +109,8 @@ import Data.Bifunctor |
| 108 | 109 | import Data.Foldable
|
| 109 | 110 | import Data.List.NonEmpty ( NonEmpty (..) )
|
| 110 | 111 | import qualified Data.List.NonEmpty as NE
|
| 111 | -import Data.List ( intercalate )
|
|
| 112 | -import Data.Maybe ( maybeToList )
|
|
| 112 | +import Data.List ( intercalate, sort )
|
|
| 113 | +import Data.Maybe ( mapMaybe, maybeToList )
|
|
| 113 | 114 | import Data.Typeable ( Typeable )
|
| 114 | 115 | import Numeric.Natural ( Natural )
|
| 115 | 116 | import Text.Printf ( printf )
|
| ... | ... | @@ -277,6 +278,13 @@ class (Outputable (DiagnosticHint a), HasDefaultDiagnosticOpts (DiagnosticOpts a |
| 277 | 278 | -- #18516 tracks our progress toward this goal.
|
| 278 | 279 | diagnosticCode :: a -> Maybe DiagnosticCode
|
| 279 | 280 | |
| 281 | + -- | Additional locations related to this diagnostic.
|
|
| 282 | + --
|
|
| 283 | + -- When rendering caret diagnostics, these locations are shown alongside the
|
|
| 284 | + -- message's primary location.
|
|
| 285 | + diagnosticRelatedLocations :: a -> [SrcSpan]
|
|
| 286 | + diagnosticRelatedLocations _ = []
|
|
| 287 | + |
|
| 280 | 288 | -- | An existential wrapper around an unknown diagnostic.
|
| 281 | 289 | data UnknownDiagnostic opts hint where
|
| 282 | 290 | UnknownDiagnostic :: (Diagnostic a, Typeable a)
|
| ... | ... | @@ -295,6 +303,7 @@ instance (HasDefaultDiagnosticOpts opts, Outputable hint) => Diagnostic (Unknown |
| 295 | 303 | diagnosticReason (UnknownDiagnostic _ _ diag) = diagnosticReason diag
|
| 296 | 304 | diagnosticHints (UnknownDiagnostic _ f diag) = map f (diagnosticHints diag)
|
| 297 | 305 | diagnosticCode (UnknownDiagnostic _ _ diag) = diagnosticCode diag
|
| 306 | + diagnosticRelatedLocations (UnknownDiagnostic _ _ diag) = diagnosticRelatedLocations diag
|
|
| 298 | 307 | |
| 299 | 308 | -- A fallback 'DiagnosticOpts' which can be used when there are no options
|
| 300 | 309 | -- for a particular diagnostic.
|
| ... | ... | @@ -785,7 +794,25 @@ getSeverityColour severity = case severity of |
| 785 | 794 | SevIgnore -> const mempty
|
| 786 | 795 | |
| 787 | 796 | getCaretDiagnostic :: MessageClass -> SrcSpan -> IO SDoc
|
| 788 | -getCaretDiagnostic msg_class (RealSrcSpan span _) =
|
|
| 797 | +getCaretDiagnostic msg_class span = getCaretDiagnostics msg_class (span :| [])
|
|
| 798 | + |
|
| 799 | +getCaretDiagnostics :: MessageClass -> NonEmpty SrcSpan -> IO SDoc
|
|
| 800 | +getCaretDiagnostics msg_class spans = do
|
|
| 801 | + let realSpans = dedupSortedRealSpans spans
|
|
| 802 | + maxMarginWidth =
|
|
| 803 | + foldl' (\acc s -> max acc (length (show (srcSpanStartLine s)))) 0 realSpans
|
|
| 804 | + vcat <$> traverse (getSingleCaretDiagnostic msg_class maxMarginWidth) realSpans
|
|
| 805 | + where
|
|
| 806 | + dedupSortedRealSpans :: NonEmpty SrcSpan -> [RealSrcSpan]
|
|
| 807 | + dedupSortedRealSpans = go Nothing . sort . mapMaybe srcSpanToRealSrcSpan . NE.toList
|
|
| 808 | + where
|
|
| 809 | + go _ [] = []
|
|
| 810 | + go prev (span:rest)
|
|
| 811 | + | Just span == prev = go prev rest
|
|
| 812 | + | otherwise = span : go (Just span) rest
|
|
| 813 | + |
|
| 814 | +getSingleCaretDiagnostic :: MessageClass -> Int -> RealSrcSpan -> IO SDoc
|
|
| 815 | +getSingleCaretDiagnostic msg_class maxMarginWidth span =
|
|
| 789 | 816 | caretDiagnostic <$> getSrcLine (srcSpanFile span) row
|
| 790 | 817 | where
|
| 791 | 818 | getSrcLine fn i =
|
| ... | ... | @@ -848,9 +875,9 @@ getCaretDiagnostic msg_class (RealSrcSpan span _) = |
| 848 | 875 | | otherwise = srcSpanEndCol span - 1
|
| 849 | 876 | width = max 1 (end - start)
|
| 850 | 877 | |
| 851 | - marginWidth = length rowStr
|
|
| 878 | + marginWidth = maxMarginWidth
|
|
| 852 | 879 | marginSpace = replicate marginWidth ' ' ++ " |"
|
| 853 | - marginRow = rowStr ++ " |"
|
|
| 880 | + marginRow = replicate (marginWidth - length rowStr) ' ' ++ rowStr ++ " |"
|
|
| 854 | 881 | |
| 855 | 882 | (srcLinePre, srcLineRest) = splitAt start srcLine
|
| 856 | 883 | (srcLineSpan, srcLinePost) = splitAt width srcLineRest
|
| ... | ... | @@ -858,7 +885,6 @@ getCaretDiagnostic msg_class (RealSrcSpan span _) = |
| 858 | 885 | caretEllipsis | multiline = "..."
|
| 859 | 886 | | otherwise = ""
|
| 860 | 887 | caretLine = replicate start ' ' ++ replicate width '^' ++ caretEllipsis
|
| 861 | -getCaretDiagnostic _ _ = pure empty
|
|
| 862 | 888 | --
|
| 863 | 889 | -- Queries
|
| 864 | 890 | --
|
| ... | ... | @@ -84,7 +84,7 @@ import GHC.Prelude |
| 84 | 84 | import GHC.Driver.Flags
|
| 85 | 85 | import GHC.Types.Error
|
| 86 | 86 | ( MessageClass (..), Severity (..)
|
| 87 | - , mkLocMessageWarningGroups,getCaretDiagnostic )
|
|
| 87 | + , mkLocMessageWarningGroups, getCaretDiagnostics )
|
|
| 88 | 88 | -- import GHC.Types.Error ()
|
| 89 | 89 | import GHC.Types.SrcLoc
|
| 90 | 90 | |
| ... | ... | @@ -102,6 +102,7 @@ import System.FilePath ( takeDirectory, (</>) ) |
| 102 | 102 | import qualified Data.Map.Strict as Map
|
| 103 | 103 | import Data.Map.Strict (Map)
|
| 104 | 104 | import Data.List (stripPrefix)
|
| 105 | +import Data.List.NonEmpty (NonEmpty(..))
|
|
| 105 | 106 | import Data.Time
|
| 106 | 107 | import System.IO
|
| 107 | 108 | import Control.Monad
|
| ... | ... | @@ -423,7 +424,8 @@ defaultLogActionWithHandles out err logflags msg_class srcSpan msg |
| 423 | 424 | MCInfo -> printErrs msg
|
| 424 | 425 | MCFatal -> printErrs msg
|
| 425 | 426 | MCDiagnostic SevIgnore _ _ -> pure () -- suppress the message
|
| 426 | - MCDiagnostic _sev _rea _code -> decorateDiagnostic logflags msg_class srcSpan msg >>= printErrs
|
|
| 427 | + MCDiagnostic _sev _rea _code ->
|
|
| 428 | + decorateDiagnostic logflags msg_class srcSpan (srcSpan :| []) msg >>= printErrs
|
|
| 427 | 429 | where
|
| 428 | 430 | printOut = defaultLogActionHPrintDoc logflags False out
|
| 429 | 431 | printErrs = defaultLogActionHPrintDoc logflags False err
|
| ... | ... | @@ -463,8 +465,8 @@ defaultLogActionWithHandles out err logflags msg_class srcSpan msg |
| 463 | 465 | -- `defaultLogActionWithHandles`)
|
| 464 | 466 | --
|
| 465 | 467 | -- This story is tracked by #24113.
|
| 466 | -decorateDiagnostic :: LogFlags -> MessageClass -> SrcSpan -> SDoc -> IO SDoc
|
|
| 467 | -decorateDiagnostic logflags msg_class srcSpan msg = addCaret
|
|
| 468 | +decorateDiagnostic :: LogFlags -> MessageClass -> SrcSpan -> NonEmpty SrcSpan -> SDoc -> IO SDoc
|
|
| 469 | +decorateDiagnostic logflags msg_class srcSpan sourceSpans msg = addCaret
|
|
| 468 | 470 | where
|
| 469 | 471 | -- Pretty print the warning flag, if any (#10752)
|
| 470 | 472 | message :: SDoc
|
| ... | ... | @@ -474,7 +476,7 @@ decorateDiagnostic logflags msg_class srcSpan msg = addCaret |
| 474 | 476 | addCaret = do
|
| 475 | 477 | caretDiagnostic <-
|
| 476 | 478 | if log_show_caret logflags
|
| 477 | - then getCaretDiagnostic msg_class srcSpan
|
|
| 479 | + then getCaretDiagnostics msg_class sourceSpans
|
|
| 478 | 480 | else pure empty
|
| 479 | 481 | return $ getPprStyle $ \style ->
|
| 480 | 482 | withPprStyle (setStyleColoured True style)
|
| 1 | - |
|
| 2 | 1 | T14114.hs:4:20: error: [GHC-10498]
|
| 3 | 2 | • Conflicting definitions for ‘a’
|
| 4 | 3 | Bound at: T14114.hs:4:20
|
| 5 | 4 | T14114.hs:4:22
|
| 6 | 5 | • In a pattern synonym declaration
|
| 6 | + |
|
|
| 7 | +4 | pattern Foo1 a <- (a,a)
|
|
| 8 | + | ^
|
|
| 9 | + |
|
|
| 10 | +4 | pattern Foo1 a <- (a,a)
|
|
| 11 | + | ^
|
|
| 7 | 12 | |
| 8 | 13 | T14114.hs:5:20: error: [GHC-10498]
|
| 9 | 14 | • Conflicting definitions for ‘a’
|
| 10 | 15 | Bound at: T14114.hs:5:20
|
| 11 | 16 | T14114.hs:5:22
|
| 12 | 17 | • In a pattern synonym declaration
|
| 18 | + |
|
|
| 19 | +5 | pattern Foo2 a = (a,a)
|
|
| 20 | + | ^
|
|
| 21 | + |
|
|
| 22 | +5 | pattern Foo2 a = (a,a)
|
|
| 23 | + | ^
|
|
| 13 | 24 | |
| 14 | 25 | T14114.hs:6:20: error: [GHC-10498]
|
| 15 | 26 | • Conflicting definitions for ‘a’
|
| 16 | 27 | Bound at: T14114.hs:6:20
|
| 17 | 28 | T14114.hs:6:22
|
| 18 | 29 | • In a pattern synonym declaration
|
| 30 | + |
|
|
| 31 | +6 | pattern Foo3 a <- (a,a) where
|
|
| 32 | + | ^
|
|
| 33 | + |
|
|
| 34 | +6 | pattern Foo3 a <- (a,a) where
|
|
| 35 | + | ^
|
|
| 36 | + |
| ... | ... | @@ -40,7 +40,7 @@ test('T26465', normal, compile_fail, ['']) |
| 40 | 40 | test('T13349', normal, compile_fail, [''])
|
| 41 | 41 | test('T13470', normal, compile_fail, [''])
|
| 42 | 42 | test('T14112', normal, compile_fail, [''])
|
| 43 | -test('T14114', normal, compile_fail, [''])
|
|
| 43 | +test('T14114', normal, compile_fail, ['-fdiagnostics-show-caret'])
|
|
| 44 | 44 | test('T14507', normal, compile_fail, ['-dsuppress-uniques'])
|
| 45 | 45 | test('T15289', normal, compile_fail, [''])
|
| 46 | 46 | test('T15685', normal, compile_fail, [''])
|
| 1 | 1 | test('rnfail001', normal, compile_fail, [''])
|
| 2 | 2 | test('rnfail002', normal, compile_fail, [''])
|
| 3 | 3 | test('rnfail003', normal, compile_fail, [''])
|
| 4 | -test('rnfail004', normal, compile_fail, [''])
|
|
| 4 | +test('rnfail004', normal, compile_fail, ['-fdiagnostics-show-caret'])
|
|
| 5 | 5 | test('rnfail007', normal, compile_fail, [''])
|
| 6 | 6 | test('rnfail008', normal, compile_fail, [''])
|
| 7 | 7 | test('rnfail009', normal, compile_fail, [''])
|
| 1 | - |
|
| 2 | -rnfail004.hs:6:5: [GHC-10498]
|
|
| 1 | +rnfail004.hs:6:5: error: [GHC-10498]
|
|
| 3 | 2 | Conflicting definitions for ‘a’
|
| 4 | 3 | Bound at: rnfail004.hs:6:5
|
| 5 | 4 | rnfail004.hs:7:10
|
| 5 | + |
|
|
| 6 | +6 | a = []
|
|
| 7 | + | ^
|
|
| 8 | + |
|
|
| 9 | +7 | (b,c,a) = ([],[],d)
|
|
| 10 | + | ^
|
|
| 6 | 11 | |
| 7 | -rnfail004.hs:7:6: [GHC-10498]
|
|
| 12 | +rnfail004.hs:7:6: error: [GHC-10498]
|
|
| 8 | 13 | Conflicting definitions for ‘b’
|
| 9 | 14 | Bound at: rnfail004.hs:7:6
|
| 10 | 15 | rnfail004.hs:8:8
|
| 16 | + |
|
|
| 17 | +7 | (b,c,a) = ([],[],d)
|
|
| 18 | + | ^
|
|
| 19 | + |
|
|
| 20 | +8 | [d,b,_] = ([],a,[])
|
|
| 21 | + | ^
|
|
| 22 | + |
| ... | ... | @@ -31,7 +31,7 @@ test('tcfail034', normal, compile_fail, ['']) |
| 31 | 31 | test('tcfail035', normal, compile_fail, [''])
|
| 32 | 32 | test('tcfail036', normal, compile_fail, [''])
|
| 33 | 33 | test('tcfail037', normal, compile_fail, [''])
|
| 34 | -test('tcfail038', normal, compile_fail, [''])
|
|
| 34 | +test('tcfail038', normal, compile_fail, ['-fdiagnostics-show-caret'])
|
|
| 35 | 35 | test('tcfail040', normal, compile_fail, [''])
|
| 36 | 36 | test('tcfail041', normal, compile_fail, [''])
|
| 37 | 37 | test('tcfail042', normal, compile_fail, [''])
|
| 1 | - |
|
| 2 | -tcfail038.hs:7:11: [GHC-10498]
|
|
| 1 | +tcfail038.hs:7:11: error: [GHC-10498]
|
|
| 3 | 2 | Conflicting definitions for ‘==’
|
| 4 | 3 | Bound at: tcfail038.hs:7:11-12
|
| 5 | 4 | tcfail038.hs:9:11-12
|
| 5 | + |
|
|
| 6 | +7 | a == b = True
|
|
| 7 | + | ^^
|
|
| 8 | + |
|
|
| 9 | +9 | a == b = False
|
|
| 10 | + | ^^
|
|
| 6 | 11 | |
| 7 | -tcfail038.hs:8:11: [GHC-10498]
|
|
| 12 | +tcfail038.hs:8:11: error: [GHC-10498]
|
|
| 8 | 13 | Conflicting definitions for ‘/=’
|
| 9 | 14 | Bound at: tcfail038.hs:8:11-12
|
| 10 | 15 | tcfail038.hs:10:11-12
|
| 16 | + |
|
|
| 17 | + 8 | a /= b = False
|
|
| 18 | + | ^^
|
|
| 19 | + |
|
|
| 20 | +10 | a /= b = True
|
|
| 21 | + | ^^
|
|
| 22 | + |