Hannes Siebenhandl pushed to branch wip/fendor/has-field-hie at Glasgow Haskell Compiler / GHC
Commits:
-
f1e31835
by fendor at 2026-09-16T15:31:55+02:00
-
5c3ce3fe
by fendor at 2026-09-16T15:32:17+02:00
-
bb8f0d26
by fendor at 2026-09-16T15:32:23+02:00
9 changed files:
- + changelog.d/record-dot-evidence
- compiler/GHC/Iface/Ext/Ast.hs
- compiler/GHC/Iface/Ext/Types.hs
- compiler/GHC/Iface/Ext/Utils.hs
- testsuite/tests/hiefile/should_run/HasFieldQueries.stdout
- + testsuite/tests/hiefile/should_run/HieTypeable.hs
- + testsuite/tests/hiefile/should_run/HieTypeable.stdout
- testsuite/tests/hiefile/should_run/RecordDotTypes.stdout
- testsuite/tests/hiefile/should_run/all.T
Changes:
| 1 | +section: compiler
|
|
| 2 | +synopsis: Record selector functions as evidence for `HasField` constraints during .hie file generation.
|
|
| 3 | +description: {
|
|
| 4 | + We record the record selector `foo` of an expression such as `f.foo` as evidence for
|
|
| 5 | + solving the constraint `HasField "foo" record ty`.
|
|
| 6 | + |
|
| 7 | + This allows us to point to the definition source location of the record selector `foo`, allowing
|
|
| 8 | + users to jump to definition using the evidence variables.
|
|
| 9 | +}
|
|
| 10 | + |
|
| 11 | +issues: #25418
|
|
| 12 | +mrs: !16694 |
| ... | ... | @@ -23,7 +23,6 @@ import GHC.Core.DataCon ( dataConWrapperType ) |
| 23 | 23 | import GHC.Core.Type ( Type, ForAllTyFlag(..) )
|
| 24 | 24 | import GHC.Core.TyCon ( TyCon, tyConClass_maybe )
|
| 25 | 25 | import GHC.Core.InstEnv
|
| 26 | -import GHC.Core.Predicate ( isEvId )
|
|
| 27 | 26 | |
| 28 | 27 | import GHC.Hs
|
| 29 | 28 | import GHC.Hs.Syn.Type
|
| ... | ... | @@ -33,7 +32,7 @@ import GHC.Types.Basic |
| 33 | 32 | import GHC.Types.UnresolvedImport ( isGeneratedImport )
|
| 34 | 33 | import GHC.Types.FieldLabel
|
| 35 | 34 | import GHC.Types.Avail ( Avails )
|
| 36 | -import GHC.Types.Id ( isDataConId_maybe )
|
|
| 35 | +import GHC.Types.Id ( isDataConId_maybe, isId, idDetails )
|
|
| 37 | 36 | import GHC.Types.Name ( Name, nameSrcSpan, nameUnique, wiredInNameTyThing_maybe, getName )
|
| 38 | 37 | import GHC.Types.Name.Env ( NameEnv, emptyNameEnv, extendNameEnv, lookupNameEnv )
|
| 39 | 38 | import GHC.Types.Name.Reader ( RecFieldInfo(..), WithUserRdr(..) )
|
| ... | ... | @@ -81,6 +80,7 @@ import Control.Applicative ( (<|>) ) |
| 81 | 80 | import GHC.Types.TypeEnv ( TypeEnv )
|
| 82 | 81 | import Control.Arrow ( second )
|
| 83 | 82 | import Data.Traversable ( mapAccumR )
|
| 83 | +import GHC.Types.Id.Info (IdDetails(..), recSelParentName)
|
|
| 84 | 84 | |
| 85 | 85 | {- Note [Updating HieAst for changes in the GHC AST]
|
| 86 | 86 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| ... | ... | @@ -682,7 +682,7 @@ instance ToHie (Context (Located (WithUserRdr Name))) where |
| 682 | 682 | |
| 683 | 683 | hieEvIdsOfTerm :: EvTerm -> [EvId]
|
| 684 | 684 | -- Returns only EvIds satisfying relevantEvId
|
| 685 | -hieEvIdsOfTerm = runFVSelectiveList isEvId . evTermFVs
|
|
| 685 | +hieEvIdsOfTerm = runFVSelectiveList isId . evTermFVs
|
|
| 686 | 686 | |
| 687 | 687 | instance ToHie (EvBindContext (LocatedA TcEvBinds)) where
|
| 688 | 688 | toHie (EvBindContext sc sp (L span (EvBinds bs)))
|
| ... | ... | @@ -690,7 +690,7 @@ instance ToHie (EvBindContext (LocatedA TcEvBinds)) where |
| 690 | 690 | where
|
| 691 | 691 | go evbind = do
|
| 692 | 692 | let evDeps = hieEvIdsOfTerm $ eb_rhs evbind
|
| 693 | - depNames = EvBindDeps $ map varName evDeps
|
|
| 693 | + depNames = EvBindDeps $ map classifyEvBindDep evDeps
|
|
| 694 | 694 | concatM $
|
| 695 | 695 | [ toHie (C (EvidenceVarBind (EvLetBind depNames) (combineScopes sc (mkScope span)) sp)
|
| 696 | 696 | (L span $ eb_lhs evbind))
|
| ... | ... | @@ -698,6 +698,12 @@ instance ToHie (EvBindContext (LocatedA TcEvBinds)) where |
| 698 | 698 | ]
|
| 699 | 699 | toHie _ = pure []
|
| 700 | 700 | |
| 701 | +classifyEvBindDep :: EvId -> EvBindDep
|
|
| 702 | +classifyEvBindDep evId =
|
|
| 703 | + case idDetails evId of
|
|
| 704 | + RecSelId{sel_tycon} -> RecordField (varName evId) (recSelParentName sel_tycon)
|
|
| 705 | + _ -> EvidenceVar (varName evId)
|
|
| 706 | + |
|
| 701 | 707 | instance ToHie (LocatedA HsWrapper) where
|
| 702 | 708 | toHie (L osp wrap)
|
| 703 | 709 | = case wrap of
|
| ... | ... | @@ -598,17 +598,61 @@ instance Outputable EvVarSource where |
| 598 | 598 | ppr (EvInstBind True cls) = text "bound due to a superclass of " <+> ppr cls
|
| 599 | 599 | ppr (EvLetBind deps) = text "bound by a let, depending on:" <+> ppr deps
|
| 600 | 600 | |
| 601 | +data EvBindDep
|
|
| 602 | + = EvidenceVar Name
|
|
| 603 | + | RecordField Name Name
|
|
| 604 | + | TypeableEvidence Name
|
|
| 605 | + deriving (Eq, Ord)
|
|
| 606 | + |
|
| 607 | +instance Outputable EvBindDep where
|
|
| 608 | + ppr = \ case
|
|
| 609 | + EvidenceVar n -> ppr n
|
|
| 610 | + RecordField sel parent -> ppr sel <+> text "of Record" <+> ppr parent
|
|
| 611 | + TypeableEvidence n -> ppr n <+> text "bound by Typeable"
|
|
| 612 | + |
|
| 613 | +evBindDepName :: EvBindDep -> Name
|
|
| 614 | +evBindDepName = \ case
|
|
| 615 | + EvidenceVar n -> n
|
|
| 616 | + RecordField selector _record -> selector
|
|
| 617 | + TypeableEvidence n -> n
|
|
| 618 | + |
|
| 619 | +evBindDepHieName :: EvBindDep -> HieName
|
|
| 620 | +evBindDepHieName = toHieName . evBindDepName
|
|
| 621 | + |
|
| 622 | +instance Binary EvBindDep where
|
|
| 623 | + put_ bh = \ case
|
|
| 624 | + EvidenceVar n -> do
|
|
| 625 | + putByte bh 0
|
|
| 626 | + put_ bh n
|
|
| 627 | + RecordField n sel -> do
|
|
| 628 | + putByte bh 1
|
|
| 629 | + put_ bh n
|
|
| 630 | + put_ bh sel
|
|
| 631 | + TypeableEvidence n -> do
|
|
| 632 | + putByte bh 2
|
|
| 633 | + put_ bh n
|
|
| 634 | + |
|
| 635 | + get bh =
|
|
| 636 | + getByte bh >>= \ case
|
|
| 637 | + 0 -> EvidenceVar <$> get bh
|
|
| 638 | + 1 -> RecordField <$> get bh <*> get bh
|
|
| 639 | + 2 -> TypeableEvidence <$> get bh
|
|
| 640 | + t -> fail $ "EvBindDep: Unknown tag: " ++ show t
|
|
| 641 | + |
|
| 601 | 642 | -- | Eq/Ord instances compare on the converted HieName,
|
| 602 | 643 | -- as non-exported names may have different uniques after
|
| 603 | 644 | -- a roundtrip
|
| 604 | -newtype EvBindDeps = EvBindDeps { getEvBindDeps :: [Name] }
|
|
| 645 | +newtype EvBindDeps = EvBindDeps { getEvBindDeps :: [EvBindDep] }
|
|
| 605 | 646 | deriving Outputable
|
| 606 | 647 | |
| 648 | +evBindDepsNames :: EvBindDeps -> [Name]
|
|
| 649 | +evBindDepsNames = map evBindDepName . getEvBindDeps
|
|
| 650 | + |
|
| 607 | 651 | instance Eq EvBindDeps where
|
| 608 | - (==) = coerce ((==) `on` map toHieName)
|
|
| 652 | + (==) = coerce ((==) `on` map evBindDepHieName)
|
|
| 609 | 653 | |
| 610 | 654 | instance Ord EvBindDeps where
|
| 611 | - compare = coerce (compare `on` map toHieName)
|
|
| 655 | + compare = coerce (compare `on` map evBindDepHieName)
|
|
| 612 | 656 | |
| 613 | 657 | instance Binary EvBindDeps where
|
| 614 | 658 | put_ bh (EvBindDeps xs) = put_ bh xs
|
| ... | ... | @@ -145,7 +145,7 @@ getEvidenceTree refmap var = go emptyNameSet var |
| 145 | 145 | det <- S.toList $ identInfo dets
|
| 146 | 146 | case det of
|
| 147 | 147 | EvidenceVarBind src@(EvLetBind (getEvBindDeps -> xs)) scp spn ->
|
| 148 | - pure $ Just ((src,scp,spn),mapMaybe (go $ extendNameSet seen var) xs)
|
|
| 148 | + pure $ Just ((src,scp,spn),mapMaybe (go (extendNameSet seen var) . evBindDepName) xs)
|
|
| 149 | 149 | EvidenceVarBind src scp spn -> pure $ Just ((src,scp,spn),[])
|
| 150 | 150 | _ -> pure Nothing
|
| 151 | 151 | pure $ Tree.Node (EvidenceInfo var sp typ (Just evdet)) children
|
| ... | ... | @@ -390,7 +390,7 @@ definedInAsts asts n = case nameSrcSpan n of |
| 390 | 390 | |
| 391 | 391 | getEvidenceBindDeps :: ContextInfo -> [Name]
|
| 392 | 392 | getEvidenceBindDeps (EvidenceVarBind (EvLetBind xs) _ _) =
|
| 393 | - getEvBindDeps xs
|
|
| 393 | + evBindDepsNames xs
|
|
| 394 | 394 | getEvidenceBindDeps _ = []
|
| 395 | 395 | |
| 396 | 396 | isEvidenceBind :: ContextInfo -> Bool
|
| ... | ... | @@ -3,16 +3,23 @@ At point (13,17), we found: |
| 3 | 3 | ==========================
|
| 4 | 4 | ┌
|
| 5 | 5 | │ $dHasField at HasFieldQueries.hs:1:1, of type: HasField "field1" Thing Char
|
| 6 | -│ is an evidence variable bound by a let, depending on: [C:HasField]
|
|
| 6 | +│ is an evidence variable bound by a let, depending on: [C:HasField,
|
|
| 7 | +│ field1 of Record Thing]
|
|
| 7 | 8 | │ with scope: ModuleScope
|
| 8 | 9 | │
|
| 9 | 10 | │ Defined at <no location info>
|
| 10 | 11 | └
|
| 11 | 12 | |
|
| 13 | ++- ┌
|
|
| 14 | +| │ C:HasField at HasFieldQueries.hs:1:1, of type: forall {k} (x :: k) r a. (r -> a) -> HasField x r a
|
|
| 15 | +| │ is a usage of an external evidence variable
|
|
| 16 | +| │ Defined in `GHC.Internal.Records'
|
|
| 17 | +| └
|
|
| 18 | +|
|
|
| 12 | 19 | `- ┌
|
| 13 | - │ C:HasField at HasFieldQueries.hs:1:1, of type: forall {k} (x :: k) r a. (r -> a) -> HasField x r a
|
|
| 20 | + │ field1 at HasFieldQueries.hs:1:1, of type: Thing -> Char
|
|
| 14 | 21 | │ is a usage of an external evidence variable
|
| 15 | - │ Defined in `GHC.Internal.Records'
|
|
| 22 | + │ Defined at HasFieldQueries.hs:9:21
|
|
| 16 | 23 | └
|
| 17 | 24 | |
| 18 | 25 | ==========================
|
| ... | ... | @@ -23,16 +30,23 @@ At point (16,27), we found: |
| 23 | 30 | ==========================
|
| 24 | 31 | ┌
|
| 25 | 32 | │ $dHasField at HasFieldQueries.hs:16:1-32, of type: HasField "field1" Thing Char
|
| 26 | -│ is an evidence variable bound by a let, depending on: [C:HasField]
|
|
| 33 | +│ is an evidence variable bound by a let, depending on: [C:HasField,
|
|
| 34 | +│ field1 of Record Thing]
|
|
| 27 | 35 | │ with scope: LocalScope HasFieldQueries.hs:16:1-32
|
| 28 | 36 | │ bound at: HasFieldQueries.hs:16:1-32
|
| 29 | 37 | │ Defined at <no location info>
|
| 30 | 38 | └
|
| 31 | 39 | |
|
| 40 | ++- ┌
|
|
| 41 | +| │ C:HasField at HasFieldQueries.hs:1:1, of type: forall {k} (x :: k) r a. (r -> a) -> HasField x r a
|
|
| 42 | +| │ is a usage of an external evidence variable
|
|
| 43 | +| │ Defined in `GHC.Internal.Records'
|
|
| 44 | +| └
|
|
| 45 | +|
|
|
| 32 | 46 | `- ┌
|
| 33 | - │ C:HasField at HasFieldQueries.hs:1:1, of type: forall {k} (x :: k) r a. (r -> a) -> HasField x r a
|
|
| 47 | + │ field1 at HasFieldQueries.hs:1:1, of type: Thing -> Char
|
|
| 34 | 48 | │ is a usage of an external evidence variable
|
| 35 | - │ Defined in `GHC.Internal.Records'
|
|
| 49 | + │ Defined at HasFieldQueries.hs:9:21
|
|
| 36 | 50 | └
|
| 37 | 51 | |
| 38 | 52 | ==========================
|
| ... | ... | @@ -40,16 +54,23 @@ At point (23,17), we found: |
| 40 | 54 | ==========================
|
| 41 | 55 | ┌
|
| 42 | 56 | │ $dHasField at HasFieldQueries.hs:1:1, of type: HasField "nested1" NestedThing Thing
|
| 43 | -│ is an evidence variable bound by a let, depending on: [C:HasField]
|
|
| 57 | +│ is an evidence variable bound by a let, depending on: [C:HasField,
|
|
| 58 | +│ nested1 of Record NestedThing]
|
|
| 44 | 59 | │ with scope: ModuleScope
|
| 45 | 60 | │
|
| 46 | 61 | │ Defined at <no location info>
|
| 47 | 62 | └
|
| 48 | 63 | |
|
| 64 | ++- ┌
|
|
| 65 | +| │ C:HasField at HasFieldQueries.hs:1:1, of type: forall {k} (x :: k) r a. (r -> a) -> HasField x r a
|
|
| 66 | +| │ is a usage of an external evidence variable
|
|
| 67 | +| │ Defined in `GHC.Internal.Records'
|
|
| 68 | +| └
|
|
| 69 | +|
|
|
| 49 | 70 | `- ┌
|
| 50 | - │ C:HasField at HasFieldQueries.hs:1:1, of type: forall {k} (x :: k) r a. (r -> a) -> HasField x r a
|
|
| 71 | + │ nested1 at HasFieldQueries.hs:1:1, of type: NestedThing -> Thing
|
|
| 51 | 72 | │ is a usage of an external evidence variable
|
| 52 | - │ Defined in `GHC.Internal.Records'
|
|
| 73 | + │ Defined at HasFieldQueries.hs:20:34
|
|
| 53 | 74 | └
|
| 54 | 75 | |
| 55 | 76 | ==========================
|
| ... | ... | @@ -57,16 +78,23 @@ At point (23,25), we found: |
| 57 | 78 | ==========================
|
| 58 | 79 | ┌
|
| 59 | 80 | │ $dHasField at HasFieldQueries.hs:1:1, of type: HasField "field1" Thing Char
|
| 60 | -│ is an evidence variable bound by a let, depending on: [C:HasField]
|
|
| 81 | +│ is an evidence variable bound by a let, depending on: [C:HasField,
|
|
| 82 | +│ field1 of Record Thing]
|
|
| 61 | 83 | │ with scope: ModuleScope
|
| 62 | 84 | │
|
| 63 | 85 | │ Defined at <no location info>
|
| 64 | 86 | └
|
| 65 | 87 | |
|
| 88 | ++- ┌
|
|
| 89 | +| │ C:HasField at HasFieldQueries.hs:1:1, of type: forall {k} (x :: k) r a. (r -> a) -> HasField x r a
|
|
| 90 | +| │ is a usage of an external evidence variable
|
|
| 91 | +| │ Defined in `GHC.Internal.Records'
|
|
| 92 | +| └
|
|
| 93 | +|
|
|
| 66 | 94 | `- ┌
|
| 67 | - │ C:HasField at HasFieldQueries.hs:1:1, of type: forall {k} (x :: k) r a. (r -> a) -> HasField x r a
|
|
| 95 | + │ field1 at HasFieldQueries.hs:1:1, of type: Thing -> Char
|
|
| 68 | 96 | │ is a usage of an external evidence variable
|
| 69 | - │ Defined in `GHC.Internal.Records'
|
|
| 97 | + │ Defined at HasFieldQueries.hs:9:21
|
|
| 70 | 98 | └
|
| 71 | 99 | |
| 72 | 100 | ==========================
|
| 1 | +module Main where
|
|
| 2 | + |
|
| 3 | +import Data.Dynamic
|
|
| 4 | +import Data.Typeable
|
|
| 5 | +import TestUtils
|
|
| 6 | + |
|
| 7 | +data Thing = Thing {field1 :: Char, field2 :: Bool}
|
|
| 8 | + deriving (Show, Eq)
|
|
| 9 | + |
|
| 10 | +castFromDynamic :: Dynamic -> Maybe Thing
|
|
| 11 | +castFromDynamic d = fromDynamic d
|
|
| 12 | + -- ^ this is the point
|
|
| 13 | + |
|
| 14 | +rep :: Thing -> TypeRep
|
|
| 15 | +rep d = typeOf d
|
|
| 16 | + -- ^ this is the point
|
|
| 17 | + |
|
| 18 | +points =
|
|
| 19 | + [ (11,21)
|
|
| 20 | + , (15,10)
|
|
| 21 | + ]
|
|
| 22 | + |
|
| 23 | +main = do
|
|
| 24 | + (df, hf) <- readTestHie "HieTypeable.hie"
|
|
| 25 | + let refmap = generateReferencesMap $ getAsts $ hie_asts hf
|
|
| 26 | + |
|
| 27 | + traverse (explainEv df hf refmap) points
|
|
| 28 | + return () |
| 1 | +==========================
|
|
| 2 | +At point (11,21), we found:
|
|
| 3 | +==========================
|
|
| 4 | +┌
|
|
| 5 | +│ $dTypeable at HieTypeable.hs:1:1, of type: Typeable Thing
|
|
| 6 | +│ is an evidence variable bound by a let, depending on: [$dTypeable]
|
|
| 7 | +│ with scope: ModuleScope
|
|
| 8 | +│
|
|
| 9 | +│ Defined at <no location info>
|
|
| 10 | +└
|
|
| 11 | +|
|
|
| 12 | +`- ┌
|
|
| 13 | + │ $dTypeable at HieTypeable.hs:1:1, of type: Typeable Thing
|
|
| 14 | + │ is an evidence variable bound by a let, depending on: []
|
|
| 15 | + │ with scope: ModuleScope
|
|
| 16 | + │
|
|
| 17 | + │ Defined at <no location info>
|
|
| 18 | + └
|
|
| 19 | + |
|
| 20 | +==========================
|
|
| 21 | +At point (15,10), we found:
|
|
| 22 | +==========================
|
|
| 23 | +┌
|
|
| 24 | +│ $dTypeable at HieTypeable.hs:1:1, of type: Typeable Thing
|
|
| 25 | +│ is an evidence variable bound by a let, depending on: []
|
|
| 26 | +│ with scope: ModuleScope
|
|
| 27 | +│
|
|
| 28 | +│ Defined at <no location info>
|
|
| 29 | +└
|
|
| 30 | + |
| 1 | 1 | At (22,6), got type: MyRecord
|
| 2 | 2 | At (22,20), got type: MyRecord -> Integer
|
| 3 | 3 | At (22,25), got type: MyRecord
|
| 4 | -At (22,28), got type: MyChild -> String
|
|
| 5 | -At (22,29), got type: MyChild -> String
|
|
| 6 | -At (22,6) - (22,8), got type: String
|
|
| 4 | +At (22,28), got type: MyChild -> [Char]
|
|
| 5 | +At (22,29), got type: MyChild -> [Char]
|
|
| 6 | +At (22,6) - (22,8), got type: [Char]
|
|
| 7 | 7 | At (22,17) - (22,20), got type: Integer
|
| 8 | 8 | At (22,25) - (22,27), got type: MyChild
|
| 9 | -At (22,25) - (22,29), got type: String |
|
| 9 | +At (22,25) - (22,29), got type: [Char] |
| ... | ... | @@ -11,3 +11,4 @@ test('T24544', [extra_run_opts('"' + config.libdir + '"'), extra_files(['TestUti |
| 11 | 11 | test('HieGadtConSigs', [extra_run_opts('"' + config.libdir + '"'), extra_files(['TestUtils.hs'])], compile_and_run, ['-package ghc -fwrite-ide-info'])
|
| 12 | 12 | test('T25709', [extra_run_opts('"' + config.libdir + '"'), extra_files(['TestUtils.hs'])], compile_and_run, ['-package ghc -fwrite-ide-info'])
|
| 13 | 13 | test('HasFieldQueries', [extra_run_opts('"' + config.libdir + '"'), extra_files(['TestUtils.hs'])], compile_and_run, ['-package ghc -fwrite-ide-info'])
|
| 14 | +test('HieTypeable', [extra_run_opts('"' + config.libdir + '"'), extra_files(['TestUtils.hs'])], compile_and_run, ['-package ghc -fwrite-ide-info']) |