summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoland Senn <rsx@bluewin.ch>2020-05-06 18:18:40 +0200
committerMarge Bot <ben+marge-bot@smart-cactus.org>2020-06-25 03:54:38 -0400
commit7e6d3d09d983337df30d12e5aaa96bae9b81b324 (patch)
treee376ca40ae31e8e0cbb11c501f3aae2d2c5a52eb
parent03a708ba8e8c323b07d8d2e0115d6eb59987cc02 (diff)
downloadhaskell-7e6d3d09d983337df30d12e5aaa96bae9b81b324.tar.gz
In `:break ident` allow out of scope and nested identifiers (Fix #3000)
This patch fixes the bug and implements the feature request of #3000. 1. If `Module` is a real module name and `identifier` a name of a top-level function in `Module` then `:break Module.identifer` works also for an `identifier` that is out of scope. 2. Extend the syntax for `:break identifier` to: :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. 3. To support the new functionality rewrite the code to tab complete `:break`.
-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