Skip to content

Commit d053233

Browse files
authored
Merge pull request #27 from gelisam/issue-17/error-locations
proper error locations in error messages
2 parents 3238f30 + 5606dce commit d053233

File tree

14 files changed

+210
-75
lines changed

14 files changed

+210
-75
lines changed

src/TypeLevel/Rewrite.hs

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{-# LANGUAGE LambdaCase, RecordWildCards, ViewPatterns #-}
1+
{-# LANGUAGE LambdaCase, OverloadedStrings, RecordWildCards, ViewPatterns #-}
22
module TypeLevel.Rewrite (plugin) where
33

44
import Control.Monad
@@ -8,9 +8,9 @@ import Data.Foldable
88
import Data.Traversable
99

1010
-- GHC API
11-
import GhcPlugins (eqType)
1211
import Coercion (Role(Representational), mkUnivCo)
13-
import Constraint (Ct, ctEvExpr, ctLoc, mkNonCanonical)
12+
import Constraint (CtEvidence(ctev_loc), Ct, ctEvExpr, ctLoc, mkNonCanonical)
13+
import GhcPlugins (PredType, SDoc, eqType, fsep, ppr)
1414
import Plugins (Plugin(pluginRecompile, tcPlugin), CommandLineOption, defaultPlugin, purePlugin)
1515
import TcEvidence (EvExpr, EvTerm, evCast)
1616
import TcPluginM (newWanted)
@@ -72,15 +72,17 @@ lookupTypeRules
7272
:: [CommandLineOption]
7373
-> TcPluginM [TypeRule]
7474
lookupTypeRules [] = do
75-
usage (show ["TypeLevel.Append.RightIdentity", "TypeLevel.Append.RightAssociative"])
75+
usage (show [ "TypeLevel.Append.RightIdentity" :: String
76+
, "TypeLevel.Append.RightAssociative"
77+
])
7678
"[]"
7779
lookupTypeRules fullyQualifiedTypeSynonyms = do
7880
-- ["TypeLevel.Append.RightIdentity", "TypeLevel.Append.RightAssociative"]
7981
for fullyQualifiedTypeSynonyms $ \fullyQualifiedTypeSynonym -> do
8082
-- "TypeLevel.Append.RightIdentity"
8183
case splitLastDot fullyQualifiedTypeSynonym of
8284
Nothing -> do
83-
usage (show "TypeLevel.Append.RightIdentity")
85+
usage (show ("TypeLevel.Append.RightIdentity" :: String))
8486
(show fullyQualifiedTypeSynonym)
8587
Just (moduleNameStr, tyConNameStr) -> do
8688
-- ("TypeLevel.Append", "RightIdentity")
@@ -114,6 +116,31 @@ plugin = defaultPlugin
114116
}
115117

116118

119+
mkErrCtx
120+
:: SDoc
121+
-> ErrCtxt
122+
mkErrCtx errDoc = (True, \env -> pure (env, errDoc))
123+
124+
newRuleInducedWanted
125+
:: Ct
126+
-> TypeRule
127+
-> PredType
128+
-> TcPluginM CtEvidence
129+
newRuleInducedWanted oldCt rule newPredType = do
130+
let loc = ctLoc oldCt
131+
132+
-- include the rewrite rule in the error message, if any
133+
let errMsg = fsep [ "From the typelevel rewrite rule:"
134+
, ppr (fromTypeRule rule)
135+
]
136+
let loc' = pushErrCtxtSameOrigin (mkErrCtx errMsg) loc
137+
138+
wanted <- newWanted loc' newPredType
139+
140+
-- ctLoc only copies the "arising from function X" part but not the location
141+
-- etc., so we need to copy the rest of it manually
142+
pure $ wanted { ctev_loc = loc' }
143+
117144
solve
118145
:: [TypeRule]
119146
-> [Ct] -- ^ Given constraints
@@ -142,27 +169,27 @@ solve rules givens _ wanteds = do
142169
let typeTerms = fmap toTypeTerm types
143170
let predType = fromDecomposeConstraint types
144171

145-
-- C a' b' c'
146-
let typeTerms' = fmap (applyRules typeSubst rules) typeTerms
147-
let types' = fmap fromTypeTerm typeTerms'
148-
let predType' = fromDecomposeConstraint types'
149-
150-
unless (eqType predType' predType) $ do
151-
-- co :: C a' b' c' ~R C a b c
152-
let co = mkUnivCo
153-
(PluginProv "TypeLevel.Rewrite")
154-
Representational
155-
predType'
156-
predType
157-
evWanted' <- lift $ newWanted (ctLoc wanted) predType'
158-
let wanted' = mkNonCanonical evWanted'
159-
let futureDict :: EvExpr
160-
futureDict = ctEvExpr evWanted'
161-
let replaceCt :: ReplaceCt
162-
replaceCt = ReplaceCt
163-
{ evidenceOfCorrectness = evCast futureDict co
164-
, replacedConstraint = wanted
165-
, replacementConstraints = [wanted']
166-
}
167-
tell [replaceCt]
172+
for_ (applyRules typeSubst rules typeTerms) $ \(rule, typeTerms') -> do
173+
-- C a' b' c'
174+
let types' = fmap fromTypeTerm typeTerms'
175+
let predType' = fromDecomposeConstraint types'
176+
177+
unless (eqType predType' predType) $ do
178+
-- co :: C a' b' c' ~R C a b c
179+
let co = mkUnivCo
180+
(PluginProv "TypeLevel.Rewrite")
181+
Representational
182+
predType'
183+
predType
184+
evWanted' <- lift $ newRuleInducedWanted wanted rule predType'
185+
let wanted' = mkNonCanonical evWanted'
186+
let futureDict :: EvExpr
187+
futureDict = ctEvExpr evWanted'
188+
let replaceCt :: ReplaceCt
189+
replaceCt = ReplaceCt
190+
{ evidenceOfCorrectness = evCast futureDict co
191+
, replacedConstraint = wanted
192+
, replacementConstraints = [wanted']
193+
}
194+
tell [replaceCt]
168195
pure $ combineReplaceCts replaceCts

src/TypeLevel/Rewrite/Internal/ApplyRules.hs

Lines changed: 67 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
{-# LANGUAGE ViewPatterns #-}
1+
{-# LANGUAGE LambdaCase, TupleSections, ViewPatterns #-}
22
{-# OPTIONS -Wno-name-shadowing #-}
33
module TypeLevel.Rewrite.Internal.ApplyRules where
44

55
import Control.Applicative
66
import Control.Monad
7+
import Control.Monad.Trans.Class
78
import Control.Monad.Trans.State
89
import Data.Foldable (asum, for_)
910
import Data.Map (Map)
10-
import Data.Maybe (isJust)
11+
import Data.Maybe (listToMaybe, maybeToList)
1112
import Data.Traversable
1213
import qualified Data.Map as Map
1314

@@ -27,37 +28,25 @@ import TypeLevel.Rewrite.Internal.TypeSubst
2728
import TypeLevel.Rewrite.Internal.TypeTerm
2829

2930

31+
type Subst = Map TyVar (Term TypeNode TypeEq)
32+
3033
applyRules
31-
:: TypeSubst
34+
:: Traversable t
35+
=> TypeSubst
3236
-> [TypeRule]
33-
-> TypeTerm
34-
-> TypeTerm
35-
applyRules _ []
36-
= id
37-
applyRules typeSubst rules
38-
= go (length rules * 100)
39-
where
40-
go :: Int -> TypeTerm -> TypeTerm
41-
go 0 _
42-
= error "the rewrite rules form a cycle"
43-
go fuel typeTerm
44-
= case multiRewrite typeSubst rules typeTerm of
45-
Nothing
46-
-> typeTerm
47-
Just typeTerm'
48-
-> go (fuel - 1) typeTerm'
49-
50-
51-
type Subst = Map TyVar (Term TypeNode TypeEq)
37+
-> t TypeTerm
38+
-> Maybe (TypeRule,t TypeTerm)
39+
applyRules typeSubst rules inputs
40+
= annotatedTraverseFirst (multiRewrite typeSubst rules) inputs
5241

5342
multiRewrite
5443
:: TypeSubst
5544
-> [TypeRule]
5645
-> TypeTerm
57-
-> Maybe TypeTerm
46+
-> Maybe (TypeRule, TypeTerm)
5847
multiRewrite typeSubst rules input
5948
= asum
60-
[ singleRewrite typeSubst rule input
49+
[ (rule,) <$> singleRewrite typeSubst rule input
6150
| rule <- rules
6251
]
6352

@@ -70,7 +59,7 @@ singleRewrite
7059
-> Maybe TypeTerm
7160
singleRewrite typeSubst rule input@(Fun inputF inputXS)
7261
= topLevelRewrite typeSubst rule input
73-
<|> zipRewrite inputF inputXS (fmap (singleRewrite typeSubst rule) inputXS)
62+
<|> (Fun inputF <$> traverseFirst (singleRewrite typeSubst rule) inputXS)
7463
singleRewrite typeSubst rule input
7564
= topLevelRewrite typeSubst rule input
7665

@@ -110,17 +99,57 @@ topLevelRewrite typeSubst (Rule pattern0 pattern') input0 = do
11099
$ typeSubst
111100
asum $ fmap (go pattern) possibleReplacements
112101

113-
-- >>> zipRewrite F [x,y,z] [Nothing,Nothing,Nothing]
102+
-- >>> traverseFirst (\x -> if even x then Just (10 + x) else Nothing) [1,3,5]
114103
-- Nothing
115-
-- >>> zipRewrite F [x,y,z] [Just x',Nothing,Just z']
116-
-- Just [x',y,z']
117-
zipRewrite
118-
:: TypeNode
119-
-> [TypeTerm]
120-
-> [Maybe TypeTerm]
121-
-> Maybe TypeTerm
122-
zipRewrite f inputXS intermediateXS = do
123-
guard (any isJust intermediateXS)
124-
outputXS <- for (zip inputXS intermediateXS) $ \(input, intermediate) -> do
125-
intermediate <|> pure input
126-
pure $ Fun f outputXS
104+
-- >>> traverseFirst (\x -> if even x then Just (10 + x) else Nothing) [1,2,4]
105+
-- Just [1,12,4]
106+
traverseFirst
107+
:: Traversable t
108+
=> (a -> Maybe a)
109+
-> t a
110+
-> Maybe (t a)
111+
traverseFirst f = listToMaybe . traverseAll f
112+
113+
annotatedTraverseFirst
114+
:: Traversable t
115+
=> (a -> Maybe (annotation, a))
116+
-> t a
117+
-> Maybe (annotation, t a)
118+
annotatedTraverseFirst f = listToMaybe . annotatedTraverseAll f
119+
120+
-- >>> traverseAll (\x -> if even x then Just (10 + x) else Nothing) [1,3,5]
121+
-- []
122+
-- >>> traverseAll (\x -> if even x then Just (10 + x) else Nothing) [1,2,4]
123+
-- [[1,12,4], [1,2,14]]
124+
traverseAll
125+
:: Traversable t
126+
=> (a -> Maybe a)
127+
-> t a
128+
-> [t a]
129+
traverseAll f
130+
= fmap snd
131+
. annotatedTraverseAll (fmap ((),) . f)
132+
133+
annotatedTraverseAll
134+
:: Traversable t
135+
=> (a -> Maybe (annotation, a))
136+
-> t a
137+
-> [(annotation, t a)]
138+
annotatedTraverseAll f ta = flip evalStateT Nothing $ do
139+
ta' <- for ta $ \a -> do
140+
get >>= \case
141+
Just _ -> do
142+
-- already picked one
143+
pure a
144+
Nothing -> do
145+
pickIt <- lift [True,False]
146+
if pickIt
147+
then do
148+
(annotation, a) <- lift $ maybeToList $ f a
149+
put (Just annotation)
150+
pure a
151+
else do
152+
pure a
153+
maybeAnnotation <- get
154+
annotation <- lift $ maybeToList maybeAnnotation
155+
pure (annotation, ta')

src/TypeLevel/Rewrite/Internal/DecomposedConstraint.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{-# LANGUAGE DeriveFunctor, LambdaCase, RecordWildCards, ViewPatterns #-}
1+
{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable, LambdaCase, RecordWildCards, ViewPatterns #-}
22
module TypeLevel.Rewrite.Internal.DecomposedConstraint where
33

44
import Control.Applicative
@@ -12,7 +12,7 @@ import Predicate (EqRel(NomEq), Pred(ClassPred, EqPred), classifyPredType, mkCla
1212
data DecomposedConstraint a
1313
= EqualityConstraint a a -- lhs ~ rhs
1414
| InstanceConstraint Class [a] -- C a b c
15-
deriving Functor
15+
deriving (Functor, Foldable, Traversable)
1616

1717
asEqualityConstraint
1818
:: Ct

src/TypeLevel/Rewrite/Internal/TypeNode.hs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module TypeLevel.Rewrite.Internal.TypeNode where
33

44
-- GHC API
55
import TyCon (TyCon)
6-
import Type (Type, isNumLitTy, isStrLitTy, splitTyConApp_maybe)
6+
import Type (Type, isNumLitTy, isStrLitTy, mkTyConApp, splitTyConApp_maybe)
77

88
import TypeLevel.Rewrite.Internal.TypeEq
99

@@ -26,3 +26,10 @@ toTypeNodeApp_maybe tyLit@(isStrLitTy -> Just _)
2626
= pure (TyLit (TypeEq tyLit), [])
2727
toTypeNodeApp_maybe _
2828
= Nothing
29+
30+
fromTypeNode
31+
:: TypeNode
32+
-> [Type]
33+
-> Type
34+
fromTypeNode (TyCon tyCon) args = mkTyConApp tyCon args
35+
fromTypeNode (TyLit (TypeEq tyLit)) _ = tyLit

src/TypeLevel/Rewrite/Internal/TypeRule.hs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
{-# LANGUAGE ViewPatterns #-}
1+
{-# LANGUAGE LambdaCase, ViewPatterns #-}
22
{-# OPTIONS -Wno-name-shadowing #-}
33
module TypeLevel.Rewrite.Internal.TypeRule where
44

55
-- GHC API
66
import Name (getOccString)
7-
import Type (TyVar, Type)
7+
import Predicate (mkPrimEqPred)
8+
import Type (TyVar, Type, mkTyVarTy)
89

910
-- term-rewriting API
1011
import Data.Rewriting.Rule (Rule(..))
@@ -23,3 +24,27 @@ toTypeRule_maybe (toTypeTemplate_maybe -> Just (Fun (TyCon (getOccString -> "~")
2324
= Just (Rule lhs_ rhs_)
2425
toTypeRule_maybe _
2526
= Nothing
27+
28+
fromTyVar
29+
:: TyVar
30+
-> Type
31+
fromTyVar
32+
= mkTyVarTy
33+
34+
fromTerm
35+
:: (f -> [Type] -> Type)
36+
-> (v -> Type)
37+
-> Term f v
38+
-> Type
39+
fromTerm fromF fromV = \case
40+
Var v
41+
-> fromV v
42+
Fun f args
43+
-> fromF f (fmap (fromTerm fromF fromV) args)
44+
45+
fromTypeRule
46+
:: TypeRule
47+
-> Type
48+
fromTypeRule (Rule lhs rhs)
49+
= mkPrimEqPred (fromTerm fromTypeNode fromTyVar lhs)
50+
(fromTerm fromTypeNode fromTyVar rhs)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<path>/error-messages-cases/error-location/src/ErrorLocation/Test.hs:13:5: error:
2+
• No instance for (Foo (F a b)) arising from a use of ‘foo’
3+
• From the typelevel rewrite rule: F x (F x y) ~ F x y
4+
In the expression: foo @(F a (F a b))
5+
In an equation for ‘f’: f = foo @(F a (F a b))
6+
|
7+
13 | f = foo @(F a (F a b))
8+
| ^^^^^^^^^^^^^^^^^^
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
dependencies:
2+
- base
3+
- typelevel-rewrite-rules
4+
5+
library:
6+
source-dirs: src
7+
ghc-options: -W -Wall
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{-# LANGUAGE ConstraintKinds, TypeFamilies #-}
2+
module ErrorLocation.Laws where
3+
4+
type family F a b
5+
6+
type FLaw x y = F x (F x y) ~ F x y
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{-# LANGUAGE AllowAmbiguousTypes, FlexibleContexts, ScopedTypeVariables, TypeApplications, TypeFamilies #-}
2+
{-# OPTIONS_GHC -fplugin TypeLevel.Rewrite
3+
-fplugin-opt=TypeLevel.Rewrite:ErrorLocation.Laws.FLaw #-}
4+
module ErrorLocation.Test where
5+
6+
import ErrorLocation.Laws
7+
8+
9+
class Foo a where
10+
foo :: ()
11+
12+
f :: forall a b. ()
13+
f = foo @(F a (F a b))

0 commit comments

Comments
 (0)