summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compiler/GHC/ByteCode/Types.hs10
-rw-r--r--docs/users_guide/8.12.1-notes.rst5
-rw-r--r--docs/users_guide/ghci.rst46
-rw-r--r--ghc/GHCi/UI.hs300
-rw-r--r--testsuite/tests/ghci.debugger/scripts/T17989.stdout17
-rw-r--r--testsuite/tests/ghci.debugger/scripts/T3000.hs4
-rw-r--r--testsuite/tests/ghci.debugger/scripts/T3000.script16
-rw-r--r--testsuite/tests/ghci.debugger/scripts/T3000.stdout21
-rw-r--r--testsuite/tests/ghci.debugger/scripts/T3000S.hs13
-rw-r--r--testsuite/tests/ghci.debugger/scripts/all.T1
-rw-r--r--testsuite/tests/ghci.debugger/scripts/break002.stdout4
-rw-r--r--testsuite/tests/ghci.debugger/scripts/break019.stderr2
-rw-r--r--testsuite/tests/ghci.debugger/scripts/break019.stdout1
13 files changed, 337 insertions, 103 deletions
diff --git a/compiler/GHC/ByteCode/Types.hs b/compiler/GHC/ByteCode/Types.hs
index 55ad604447..296f886a29 100644
--- a/compiler/GHC/ByteCode/Types.hs
+++ b/compiler/GHC/ByteCode/Types.hs
@@ -154,6 +154,7 @@ data ModBreaks
-- ^ An array giving the names of the free variables at each breakpoint.
, modBreaks_decls :: !(Array BreakIndex [String])
-- ^ An array giving the names of the declarations enclosing each breakpoint.
+ -- See Note [Field modBreaks_decls]
, modBreaks_ccs :: !(Array BreakIndex (RemotePtr CostCentre))
-- ^ Array pointing to cost centre for each breakpoint
, modBreaks_breakInfo :: IntMap CgBreakInfo
@@ -180,3 +181,12 @@ emptyModBreaks = ModBreaks
, modBreaks_ccs = array (0,-1) []
, modBreaks_breakInfo = IntMap.empty
}
+
+{-
+Note [Field modBreaks_decls]
+~~~~~~~~~~~~~~~~~~~~~~
+A value of eg ["foo", "bar", "baz"] in a `modBreaks_decls` field means:
+The breakpoint is in the function called "baz" that is declared in a `let`
+or `where` clause of a declaration called "bar", which itself is declared
+in a `let` or `where` clause of the top-level function called "foo".
+-}
diff --git a/docs/users_guide/8.12.1-notes.rst b/docs/users_guide/8.12.1-notes.rst
index bc2414489f..1bf9464db4 100644
--- a/docs/users_guide/8.12.1-notes.rst
+++ b/docs/users_guide/8.12.1-notes.rst
@@ -161,6 +161,11 @@ GHCi
passed as arguments: either by enclosing the file names in double quotes or by
escaping spaces in file names with a backslash. (:ghc-ticket:`18027`)
+- The GHCi debugger syntax ``:break <qualified.name>`` now allows to set
+ breakpoints on all functions. The restrictions ``top-Level`` and ``exported``
+ have been removed. Hence it's now possible to use this syntax to set
+ breakpoints on functions defined in nested ``where`` or ``let`` clauses.
+
Runtime system
~~~~~~~~~~~~~~
diff --git a/docs/users_guide/ghci.rst b/docs/users_guide/ghci.rst
index 390719ff80..9f63e0c50b 100644
--- a/docs/users_guide/ghci.rst
+++ b/docs/users_guide/ghci.rst
@@ -1520,12 +1520,52 @@ breakpoint is to name a top-level function:
.. code-block:: none
- :break identifier
+ :break identifier
Where ⟨identifier⟩ names any top-level function in an interpreted module
currently loaded into GHCi (qualified names may be used). The breakpoint
-will be set on the body of the function, when it is fully applied but
-before any pattern matching has taken place.
+will be set on the body of the function, when it is fully applied.
+If the function has several patterns, then a breakpoint will be set on
+each of them.
+
+By using qualified names, one can set breakpoints on all functions
+(top-level and nested) in every loaded and interpreted module:
+
+.. code-block:: none
+
+ :break [ModQual.]topLevelIdent[.nestedIdent]...[.nestedIdent]
+
+⟨ModQual⟩ is optional and is either the effective name of a module or
+the local alias of a qualified import statement.
+
+⟨topLevelIdent⟩ is the name of a top level function in the module
+referenced by ⟨ModQual⟩.
+
+⟨nestedIdent⟩ is optional and the name of a function nested in a let or
+where clause inside the previously mentioned function ⟨nestedIdent⟩ or
+⟨topLevelIdent⟩.
+
+If ⟨ModQual⟩ is a module name, then ⟨topLevelIdent⟩ can be any top level
+identifier in this module. If ⟨ModQual⟩ is missing or a local alias of a
+qualified import, then ⟨topLevelIdent⟩ must be in scope.
+
+Breakpoints can be set on arbitrarily deeply nested functions, but the
+whole chain of nested function names must be specified.
+
+Consider the function ``foo`` in a module ``Main``:
+
+.. code-block:: none
+
+ foo s = 'a' : add s
+ where add = (++"z")
+
+The breakpoint on the function ``add`` can be set with one of the
+following commands:
+
+.. code-block:: none
+
+ :break Main.foo.add
+ :break foo.add
Breakpoints can also be set by line (and optionally column) number:
diff --git a/ghc/GHCi/UI.hs b/ghc/GHCi/UI.hs
index 9db2dd5773..5f6bea091a 100644
--- a/ghc/GHCi/UI.hs
+++ b/ghc/GHCi/UI.hs
@@ -54,7 +54,7 @@ import GHC.Hs.ImpExp
import GHC.Hs
import GHC.Driver.Types ( tyThingParent_maybe, handleFlagWarnings, getSafeMode, hsc_IC,
setInteractivePrintName, hsc_dflags, msObjFilePath, runInteractiveHsc,
- hsc_dynLinker, hsc_interp )
+ hsc_dynLinker, hsc_interp, emptyModBreaks )
import GHC.Unit.Module
import GHC.Types.Name
import GHC.Unit.State ( unitIsTrusted, unsafeLookupUnit, unsafeLookupUnitId,
@@ -3380,67 +3380,74 @@ completeIdentifier line@(left, _) =
dflags <- GHC.getSessionDynFlags
return (filter (w `isPrefixOf`) (map (showPpr dflags) rdrs))
-
-completeBreakpoint = wrapCompleter spaces $ \w -> do -- #17989
- -- See Note [Tab-completion for :break]
- -- Pif ~ Pair with Identifier name and File name
- pifsBreaks <- pifsFromModBreaks
- pifsInscope <- pifsInscopeByPrefix w
- pure $ [n | (n,f) <- pifsInscope, (unQual n, f) `elem` pifsBreaks]
+-- TAB-completion for the :break command.
+-- Build and return a list of breakpoint identifiers with a given prefix.
+-- See Note [Tab-completion for :break]
+completeBreakpoint = wrapCompleter spaces $ \w -> do -- #3000
+ -- bid ~ breakpoint identifier = a name of a function that is
+ -- eligible to set a breakpoint.
+ let (mod_str, _, _) = splitIdent w
+ bids_mod_breaks <- bidsFromModBreaks mod_str
+ bids_inscopes <- bidsFromInscopes
+ pure $ nub $ filter (isPrefixOf w) $ bids_mod_breaks ++ bids_inscopes
where
- -- Extract from the ModBreaks data all the names of top-level
- -- functions eligible to set breakpoints, and put them
- -- into a pair together with the filename where they are defined.
- pifsFromModBreaks :: GhciMonad m => m [(String, FastString)]
- pifsFromModBreaks = do
+ -- Extract all bids from ModBreaks for a given module name prefix
+ bidsFromModBreaks :: GhciMonad m => String -> m [String]
+ bidsFromModBreaks mod_pref = do
+ imods <- interpretedHomeMods
+ let pmods = filter ((isPrefixOf mod_pref) . showModule) imods
+ nonquals <- case null mod_pref of
+ -- If the prefix is empty, then for functions declared in a module
+ -- in scope, don't qualify the function name.
+ -- (eg: `main` instead of `Main.main`)
+ True -> do
+ imports <- GHC.getContext
+ pure [ m | IIModule m <- imports]
+ False -> return []
+ bidss <- mapM (bidsByModule nonquals) pmods
+ pure $ concat bidss
+
+ -- Return a list of interpreted home modules
+ interpretedHomeMods :: GhciMonad m => m [Module]
+ interpretedHomeMods = do
graph <- GHC.getModuleGraph
- imods <- filterM GHC.moduleIsInterpreted $
- ms_mod <$> GHC.mgModSummaries graph
- topDecls <- mapM pifsFromModBreaksByModule imods
- pure $ concat topDecls
-
- -- Return all possible top-level pifs from the ModBreaks
- -- for one module.
- -- Identifiers of ModBreaks pifs are never qualified.
- pifsFromModBreaksByModule :: GhciMonad m => Module -> m [(String, FastString)]
- pifsFromModBreaksByModule mod = do
- (_, locs, decls) <- getModBreak mod
- let mbFile = safeHead $ mapMaybe srcSpanFileName_maybe $ elems locs
- -- The first element in `decls` is the name of the top-level function.
- let topLvlDecls = nub $ mapMaybe safeHead $ elems decls
- pure $ case mbFile of
- Nothing -> []
- (Just file) -> zip topLvlDecls $ repeat file
- where
- safeHead [] = Nothing
- safeHead (h : _) = Just h
-
- -- Return the pifs of all identifieres (RdrNames) in scope, where
- -- the identifier has the given prefix.
- -- Identifiers of inscope pifs maybe qualified.
- pifsInscopeByPrefix :: GhciMonad m => String -> m [(String, FastString)]
- pifsInscopeByPrefix pref = do
- dflags <- GHC.getSessionDynFlags
+ let hmods = ms_mod <$> GHC.mgModSummaries graph
+ filterM GHC.moduleIsInterpreted hmods
+
+ -- Return all possible bids for a given Module
+ bidsByModule :: GhciMonad m => [ModuleName] -> Module -> m [String]
+ bidsByModule nonquals mod = do
+ (_, _, decls) <- getModBreak mod
+ let bids = nub $ declPath <$> elems decls
+ pure $ case (moduleName mod) `elem` nonquals of
+ True -> bids
+ False -> (combineModIdent (showModule mod)) <$> bids
+
+ -- Extract all bids from all top-level identifiers in scope.
+ bidsFromInscopes :: GhciMonad m => m [String]
+ bidsFromInscopes = do
rdrs <- GHC.getRdrNamesInScope
- let strnams = (filter (pref `isPrefixOf`) (map (showPpr dflags) rdrs))
- nams_fil <- mapM createInscopePif strnams
- pure $ concat nams_fil
-
- -- Return a list of pifs for a single in scope identifier
- createInscopePif :: GhciMonad m => String -> m [(String, FastString)]
- createInscopePif str_rdr = do
+ inscopess <- mapM createInscope $ (showSDocUnsafe . ppr) <$> rdrs
+ imods <- interpretedHomeMods
+ let topLevels = filter ((`elem` imods) . snd) $ concat inscopess
+ bidss <- mapM (addNestedDecls) topLevels
+ pure $ concat bidss
+
+ -- Return a list of (bid,module) for a single top-level in-scope identifier
+ createInscope :: GhciMonad m => String -> m [(String, Module)]
+ createInscope str_rdr = do
names <- GHC.parseName str_rdr
- let files = mapMaybe srcSpanFileName_maybe $ map nameSrcSpan names
- pure $ zip (repeat str_rdr) files
-
- -- unQual "ModLev.Module.func" -> "func"
- unQual :: String -> String
- unQual qual_unqual =
- let ixs = elemIndices '.' qual_unqual
- in case ixs of
- [] -> qual_unqual
- _ -> drop (1 + last ixs) qual_unqual
-
+ pure $ zip (repeat str_rdr) $ GHC.nameModule <$> names
+
+ -- For every top-level identifier in scope, add the bids of the nested
+ -- declarations. See Note [ModBreaks.decls] in GHC.ByteCode.Types
+ addNestedDecls :: GhciMonad m => (String, Module) -> m [String]
+ addNestedDecls (ident, mod) = do
+ (_, _, decls) <- getModBreak mod
+ let (mod_str, topLvl, _) = splitIdent ident
+ ident_decls = filter ((topLvl ==) . head) $ elems decls
+ bids = nub $ declPath <$> ident_decls
+ pure $ map (combineModIdent mod_str) bids
completeModule = wrapIdentCompleter $ \w -> do
dflags <- GHC.getSessionDynFlags
@@ -3523,40 +3530,33 @@ allVisibleModules dflags = listVisibleModuleNames (unitState dflags)
completeExpression = completeQuotedWord (Just '\\') "\"" listFiles
completeIdentifier
+
{-
Note [Tab-completion for :break]
---------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In tab-completion for the `:break` command, only those
identifiers should be shown, that are accepted in the
`:break` command. Hence these identifiers must be
- defined in an interpreted module
-- top-level
-- currently in scope
- listed in a `ModBreaks` value as a possible breakpoint.
The identifiers may be qualified or unqualified.
-To get all possible top-level breakpoints for tab-completeion
+To get all possible top-level breakpoints for tab-completion
with the correct qualification do:
-1. Build the list called `pifsBreaks` of all pairs of
-(Identifier, module-filename) from the `ModBreaks` values.
-Here all identifiers are unqualified.
-
-2. Build the list called `pifInscope` of all pairs of
-(Identifiers, module-filename) with identifiers from
-the `GlobalRdrEnv`. Take only those identifiers that are
-in scope and have the correct prefix.
-Here the identifiers may be qualified.
+1. Build a list called `bids_mod_breaks` of identifier names eligible
+for setting breakpoints: For every interpreted module with the
+correct module prefix read all identifier names from the `decls` field
+of the `ModBreaks` array.
-3. From the `pifInscope` list seclect all pairs that can be
-found in the `pifsBreaks` list, by comparing only the
-unqualified part of the identifier.
-The remaining identifiers can be used for tab-completion.
+2. Build a list called `bids_inscopess` of identifiers in scope:
+Take all RdrNames in scope, and filter by interpreted modules.
+Fore each of these top-level identifiers add from the `ModBreaks`
+arrays the available identifiers of the nested functions.
-This ensures, that we show only identifiers, that can be used
-in a `:break` command.
+3.) Combine both lists, filter by the given prefix, and remove duplicates.
-}
-- -----------------------------------------------------------------------------
@@ -3791,17 +3791,7 @@ breakSwitch (arg1:rest)
[] -> do
liftIO $ putStrLn "No modules are loaded with debugging support."
| otherwise = do -- try parsing it as an identifier
- wantNameFromInterpretedModule noCanDo arg1 $ \name -> do
- maybe_info <- GHC.getModuleInfo (GHC.nameModule name)
- case maybe_info of
- Nothing -> noCanDo name (ptext (sLit "cannot get module info"))
- Just minf ->
- ASSERT( isExternalName name )
- findBreakAndSet (GHC.nameModule name) $
- findBreakForBind name (GHC.modInfoModBreaks minf)
- where
- noCanDo n why = printForUser $
- text "cannot set breakpoint on " <> ppr n <> text ": " <> why
+ breakById arg1
breakByModule :: GhciMonad m => Module -> [String] -> m ()
breakByModule md (arg1:rest)
@@ -3817,8 +3807,72 @@ breakByModuleLine md line args
findBreakAndSet md $ maybeToList . findBreakByCoord Nothing (line, read col)
| otherwise = breakSyntax
+-- Set a breakpoint for an identifier
+-- See Note [Setting Breakpoints by Id]
+breakById :: GhciMonad m => String -> m () -- #3000
+breakById inp = do
+ let (mod_str, top_level, fun_str) = splitIdent inp
+ mod_top_lvl = combineModIdent mod_str top_level
+ mb_mod <- catch (lookupModuleInscope mod_top_lvl)
+ (\(_ :: SomeException) -> lookupModuleInGraph mod_str)
+ -- If the top-level name is not in scope, `lookupModuleInscope` will
+ -- throw an exception, then lookup the module name in the module graph.
+ mb_err_msg <- validateBP mod_str fun_str mb_mod
+ case mb_err_msg of
+ Just err_msg -> printForUser $
+ text "Cannot set breakpoint on" <+> quotes (text inp)
+ <> text ":" <+> err_msg
+ Nothing -> do
+ -- No errors found, go and set the breakpoint
+ mb_mod_info <- GHC.getModuleInfo $ fromJust mb_mod
+ let modBreaks = case mb_mod_info of
+ (Just mod_info) -> GHC.modInfoModBreaks mod_info
+ Nothing -> emptyModBreaks
+ findBreakAndSet (fromJust mb_mod) $ findBreakForBind fun_str modBreaks
+ where
+ -- Try to lookup the module for an identifier that is in scope.
+ -- `parseName` throws an exception, if the identifier is not in scope
+ lookupModuleInscope :: GhciMonad m => String -> m (Maybe Module)
+ lookupModuleInscope mod_top_lvl = do
+ names <- GHC.parseName mod_top_lvl
+ pure $ Just $ head $ GHC.nameModule <$> names
+ -- if GHC.parseName succeeds `names` is not empty!
+ -- if it fails, the last line will not be evaluated.
+
+ -- Lookup the Module of a module name in the module graph
+ lookupModuleInGraph :: GhciMonad m => String -> m (Maybe Module)
+ lookupModuleInGraph mod_str = do
+ graph <- GHC.getModuleGraph
+ let hmods = ms_mod <$> GHC.mgModSummaries graph
+ pure $ find ((== mod_str) . showModule) hmods
+
+ -- Check validity of an identifier to set a breakpoint:
+ -- 1. The module of the identifier must exist
+ -- 2. the identifier must be in an interpreted module
+ -- 3. the ModBreaks array for module `mod` must have an entry
+ -- for the function
+ validateBP :: GhciMonad m => String -> String -> Maybe Module
+ -> m (Maybe SDoc)
+ validateBP mod_str fun_str Nothing = pure $ Just $ quotes (text
+ (combineModIdent mod_str (Prelude.takeWhile (/= '.') fun_str)))
+ <+> text "not in scope"
+ validateBP _ "" (Just _) = pure $ Just $ text "Function name is missing"
+ validateBP _ fun_str (Just modl) = do
+ isInterpr <- GHC.moduleIsInterpreted modl
+ (_, _, decls) <- getModBreak modl
+ mb_err_msg <- case isInterpr of
+ False -> pure $ Just $ text "Module" <+> quotes (ppr modl)
+ <+> text "is not interpreted"
+ True -> case fun_str `elem` (declPath <$> elems decls) of
+ False -> pure $ Just $
+ text "No breakpoint found for" <+> quotes (text fun_str)
+ <+> "in module" <+> quotes (ppr modl)
+ True -> pure Nothing
+ pure mb_err_msg
+
breakSyntax :: a
-breakSyntax = throwGhcException (CmdLineError "Syntax: :break [<mod>] <line> [<column>]")
+breakSyntax = throwGhcException $ CmdLineError ("Syntax: :break [<mod>.]<func>[.<func>]\n"
+ ++ " :break [<mod>] <line> [<column>]")
findBreakAndSet :: GhciMonad m
=> Module -> (TickArray -> [(Int, RealSrcSpan)]) -> m ()
@@ -3870,15 +3924,15 @@ findBreakByLine line arr
-- The aim is to find the breakpoints for all the RHSs of the
-- equations corresponding to a binding. So we find all breakpoints
-- for
--- (a) this binder only (not a nested declaration)
+-- (a) this binder only (it maybe a top-level or a nested declaration)
-- (b) that do not have an enclosing breakpoint
-findBreakForBind :: Name -> GHC.ModBreaks -> TickArray
+findBreakForBind :: String -> GHC.ModBreaks -> TickArray
-> [(BreakIndex,RealSrcSpan)]
-findBreakForBind name modbreaks _ = filter (not . enclosed) ticks
+findBreakForBind str_name modbreaks _ = filter (not . enclosed) ticks
where
ticks = [ (index, span)
- | (index, [n]) <- assocs (GHC.modBreaks_decls modbreaks),
- n == occNameString (nameOccName name),
+ | (index, decls) <- assocs (GHC.modBreaks_decls modbreaks),
+ str_name == declPath decls,
RealSrcSpan span _ <- [GHC.modBreaks_locs modbreaks ! index] ]
enclosed (_,sp0) = any subspan ticks
where subspan (_,sp) = sp /= sp0 &&
@@ -3922,6 +3976,22 @@ start_bold = "\ESC[1m"
end_bold :: String
end_bold = "\ESC[0m"
+{-
+Note [Setting Breakpoints by Id]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+To set a breakpoint first check whether a ModBreaks array contains a
+breakpoint with the given function name:
+In `:break M.foo` `M` may be a module name or a local alias of an import
+statement. To lookup a breakpoint in the ModBreaks, the effective module
+name is needed. Even if a module called `M` exists, `M` may still be
+a local alias. To get the module name, parse the top-level identifier with
+`GHC.parseName`. If this succeeds, extract the module name from the
+returned value. If it fails, catch the exception and assume `M` is a real
+module name.
+
+The names of nested functions are stored in `ModBreaks.modBreaks_decls`.
+-}
+
-----------------------------------------------------------------------------
-- :where
@@ -4211,6 +4281,14 @@ lookupModuleName mName = GHC.lookupModule mName Nothing
isMainUnitModule :: Module -> Bool
isMainUnitModule m = GHC.moduleUnit m == mainUnit
+showModule :: Module -> String
+showModule = moduleNameString . moduleName
+
+-- Return a String with the declPath of the function of a breakpoint.
+-- See Note [Field modBreaks_decls] in GHC.ByteCode.Types
+declPath :: [String] -> String
+declPath = intercalate "."
+
-- TODO: won't work if home dir is encoded.
-- (changeDirectory may not work either in that case.)
expandPath :: MonadIO m => String -> m String
@@ -4267,3 +4345,37 @@ clearAllTargets = discardActiveBreakPoints
>> GHC.setTargets []
>> GHC.load LoadAllTargets
>> pure ()
+
+-- Split up a string with an eventually qualified declaration name into 3 components
+-- 1. module name
+-- 2. top-level decl
+-- 3. full-name of the eventually nested decl, but without module qualification
+-- eg "foo" = ("", "foo", "foo")
+-- "A.B.C.foo" = ("A.B.C", "foo", "foo")
+-- "M.N.foo.bar" = ("M.N", "foo", "foo.bar")
+splitIdent :: String -> (String, String, String)
+splitIdent [] = ("", "", "")
+splitIdent inp@(a : _)
+ | (isUpper a) = case fixs of
+ [] -> (inp, "", "")
+ (i1 : [] ) -> (upto i1, from i1, from i1)
+ (i1 : i2 : _) -> (upto i1, take (i2 - i1 - 1) (from i1), from i1)
+ | otherwise = case ixs of
+ [] -> ("", inp, inp)
+ (i1 : _) -> ("", upto i1, inp)
+ where
+ ixs = elemIndices '.' inp -- indices of '.' in whole input
+ fixs = dropWhile isNextUc ixs -- indices of '.' in function names --
+ isNextUc ix = isUpper $ safeInp !! (ix+1)
+ safeInp = inp ++ " "
+ upto i = take i inp
+ from i = drop (i + 1) inp
+
+-- Qualify an identifier name with a module name
+-- combineModIdent "A" "foo" = "A.foo"
+-- combineModIdent "" "foo" = "foo"
+combineModIdent :: String -> String -> String
+combineModIdent mod ident
+ | null mod = ident
+ | null ident = mod
+ | otherwise = mod ++ "." ++ ident
diff --git a/testsuite/tests/ghci.debugger/scripts/T17989.stdout b/testsuite/tests/ghci.debugger/scripts/T17989.stdout
index ce658ace22..15ac9b62b9 100644
--- a/testsuite/tests/ghci.debugger/scripts/T17989.stdout
+++ b/testsuite/tests/ghci.debugger/scripts/T17989.stdout
@@ -1,9 +1,20 @@
-7 7 ":break "
+18 18 ":break "
"B.bar"
"B.foo"
+"B.foo.x"
+"B.foo.y"
"T17989A.bar"
"T17989A.foo"
+"T17989A.foo.x"
+"T17989A.foo.y"
+"T17989A.priv"
+"T17989B.bar"
+"T17989B.foo"
+"T17989B.foo.x"
+"T17989B.foo.y"
+"T17989B.priv"
"T17989C.foo"
+"T17989C.priv"
"foo"
"main"
Breakpoint 0 activated at T17989B.hs:10:9-25
@@ -13,8 +24,10 @@ Breakpoint 3 activated at T17989A.hs:4:9-14
Breakpoint 4 activated at T17989C.hs:4:9-26
Breakpoint 4 was already set at T17989C.hs:4:9-26
Breakpoint 5 activated at T17989M.hs:6:8-51
-2 2 ":break "
+4 4 ":break "
"B.bar"
"B.foo"
+"B.foo.x"
+"B.foo.y"
1 1 ":break "
"foo"
diff --git a/testsuite/tests/ghci.debugger/scripts/T3000.hs b/testsuite/tests/ghci.debugger/scripts/T3000.hs
new file mode 100644
index 0000000000..429b61b836
--- /dev/null
+++ b/testsuite/tests/ghci.debugger/scripts/T3000.hs
@@ -0,0 +1,4 @@
+import qualified T3000S as S
+
+main :: IO ()
+main = putStrLn $ S.sshow 7
diff --git a/testsuite/tests/ghci.debugger/scripts/T3000.script b/testsuite/tests/ghci.debugger/scripts/T3000.script
new file mode 100644
index 0000000000..4cee82d4a0
--- /dev/null
+++ b/testsuite/tests/ghci.debugger/scripts/T3000.script
@@ -0,0 +1,16 @@
+:l T3000
+:break main
+:break Main.main
+:break T3000S.sshow
+:break S.sshow
+:break T3000S.hidden
+:break T3000S.sshow.nest
+:show breaks
+-- Generate some error messages
+:break xyz
+:break sshow
+:break S.hidden
+:break S.hidden.nest
+:break Foo.xyz
+:break T3000S
+:break T3000S.xyz
diff --git a/testsuite/tests/ghci.debugger/scripts/T3000.stdout b/testsuite/tests/ghci.debugger/scripts/T3000.stdout
new file mode 100644
index 0000000000..57c8459d81
--- /dev/null
+++ b/testsuite/tests/ghci.debugger/scripts/T3000.stdout
@@ -0,0 +1,21 @@
+Breakpoint 0 activated at T3000.hs:4:8-27
+Breakpoint 0 was already set at T3000.hs:4:8-27
+Breakpoint 1 activated at T3000S.hs:9:9-27
+Breakpoint 1 was already set at T3000S.hs:9:9-27
+Breakpoint 2 activated at T3000S.hs:12:12-23
+Breakpoint 3 activated at T3000S.hs:13:12-32
+Breakpoint 4 activated at T3000S.hs:6:18-38
+Breakpoint 5 activated at T3000S.hs:7:18-36
+[0] Main T3000.hs:4:8-27 enabled
+[1] T3000S T3000S.hs:9:9-27 enabled
+[2] T3000S T3000S.hs:12:12-23 enabled
+[3] T3000S T3000S.hs:13:12-32 enabled
+[4] T3000S T3000S.hs:6:18-38 enabled
+[5] T3000S T3000S.hs:7:18-36 enabled
+Cannot set breakpoint on ‘xyz’: ‘xyz’ not in scope
+Cannot set breakpoint on ‘sshow’: ‘sshow’ not in scope
+Cannot set breakpoint on ‘S.hidden’: ‘S.hidden’ not in scope
+Cannot set breakpoint on ‘S.hidden.nest’: ‘S.hidden’ not in scope
+Cannot set breakpoint on ‘Foo.xyz’: ‘Foo.xyz’ not in scope
+Cannot set breakpoint on ‘T3000S’: Function name is missing
+Cannot set breakpoint on ‘T3000S.xyz’: No breakpoint found for ‘xyz’ in module ‘T3000S’
diff --git a/testsuite/tests/ghci.debugger/scripts/T3000S.hs b/testsuite/tests/ghci.debugger/scripts/T3000S.hs
new file mode 100644
index 0000000000..245b3963bd
--- /dev/null
+++ b/testsuite/tests/ghci.debugger/scripts/T3000S.hs
@@ -0,0 +1,13 @@
+module T3000S (sshow) where
+
+sshow :: Int -> String
+sshow n =
+ let nest :: Int -> String
+ nest 0 = " nest: " ++ hidden 0
+ nest k = " nest: " ++ show k
+ in
+ " show: " ++ nest n
+
+hidden :: Int -> String
+hidden 1 = " hidden: 1"
+hidden n = " hidden: " ++ show n
diff --git a/testsuite/tests/ghci.debugger/scripts/all.T b/testsuite/tests/ghci.debugger/scripts/all.T
index aa998adef2..bd92d0ffa8 100644
--- a/testsuite/tests/ghci.debugger/scripts/all.T
+++ b/testsuite/tests/ghci.debugger/scripts/all.T
@@ -109,6 +109,7 @@ test('T1620', extra_files(['T1620/', 'T1620/T1620.hs']),
ghci_script, ['T1620.script'])
test('T2740', normal, ghci_script, ['T2740.script'])
test('T2950', normal, ghci_script, ['T2950.script'])
+test('T3000', normal, ghci_script, ['T3000.script'])
test('getargs', extra_files(['../getargs.hs']), ghci_script, ['getargs.script'])
test('T7386', normal, ghci_script, ['T7386.script'])
diff --git a/testsuite/tests/ghci.debugger/scripts/break002.stdout b/testsuite/tests/ghci.debugger/scripts/break002.stdout
index 72e0359c7b..40f07d3a1f 100644
--- a/testsuite/tests/ghci.debugger/scripts/break002.stdout
+++ b/testsuite/tests/ghci.debugger/scripts/break002.stdout
@@ -1,2 +1,2 @@
-cannot set breakpoint on map: module GHC.Base is not interpreted
-cannot set breakpoint on map: module GHC.Base is not interpreted
+Cannot set breakpoint on ‘Data.List.map’: Module ‘GHC.Base’ is not interpreted
+Cannot set breakpoint on ‘Data.List.map’: Module ‘GHC.Base’ is not interpreted
diff --git a/testsuite/tests/ghci.debugger/scripts/break019.stderr b/testsuite/tests/ghci.debugger/scripts/break019.stderr
deleted file mode 100644
index 3cb286b7eb..0000000000
--- a/testsuite/tests/ghci.debugger/scripts/break019.stderr
+++ /dev/null
@@ -1,2 +0,0 @@
-
-<interactive>:1:1: error: Not in scope: ‘Test2’
diff --git a/testsuite/tests/ghci.debugger/scripts/break019.stdout b/testsuite/tests/ghci.debugger/scripts/break019.stdout
new file mode 100644
index 0000000000..4cc0e1d9db
--- /dev/null
+++ b/testsuite/tests/ghci.debugger/scripts/break019.stdout
@@ -0,0 +1 @@
+Cannot set breakpoint on ‘Test2’: Function name is missing