summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHaskellMouse <rinat.stryungis@serokell.io>2022-05-31 01:27:56 +0400
committerMarge Bot <ben+marge-bot@smart-cactus.org>2023-01-11 13:43:36 -0500
commit300bcc1577772b6e2848c3432efb14d89af2df76 (patch)
tree879613d969a4e270f72a83cdaf29057e05429a48
parentaed1974e92366ab8e117734f308505684f70cddf (diff)
downloadhaskell-300bcc1577772b6e2848c3432efb14d89af2df76.tar.gz
Parse qualified terms in type signatures
This commit allows qualified terms in type signatures to pass the parser and to be cathced by renamer with more informative error message. Adds a few tests. Fixes #21605
-rw-r--r--compiler/GHC/Parser.y4
-rw-r--r--compiler/GHC/Rename/Env.hs35
-rw-r--r--compiler/GHC/Rename/HsType.hs2
-rw-r--r--compiler/GHC/Rename/Unbound.hs37
-rw-r--r--compiler/GHC/Tc/Errors/Ppr.hs9
-rw-r--r--compiler/GHC/Tc/Errors/Types.hs1
-rw-r--r--compiler/GHC/Types/Error/Codes.hs1
-rw-r--r--testsuite/tests/rename/should_fail/T21605a.hs6
-rw-r--r--testsuite/tests/rename/should_fail/T21605a.stderr10
-rw-r--r--testsuite/tests/rename/should_fail/T21605b.hs7
-rw-r--r--testsuite/tests/rename/should_fail/T21605b.stderr9
-rw-r--r--testsuite/tests/rename/should_fail/T21605c.hs6
-rw-r--r--testsuite/tests/rename/should_fail/T21605c.stderr9
-rw-r--r--testsuite/tests/rename/should_fail/T21605d.hs3
-rw-r--r--testsuite/tests/rename/should_fail/T21605d.stderr8
-rw-r--r--testsuite/tests/rename/should_fail/all.T5
16 files changed, 142 insertions, 10 deletions
diff --git a/compiler/GHC/Parser.y b/compiler/GHC/Parser.y
index 7cc43b7273..2648552bee 100644
--- a/compiler/GHC/Parser.y
+++ b/compiler/GHC/Parser.y
@@ -2263,6 +2263,10 @@ atype :: { LHsType GhcPs }
| STRING { reLocA $ sLL $1 $> $ HsTyLit noExtField $ HsStrTy (getSTRINGs $1)
(getSTRING $1) }
| '_' { reLocA $ sL1 $1 $ mkAnonWildCardTy }
+ -- Type variables are never exported, so `M.tyvar` will be rejected by the renamer.
+ -- We let it pass the parser because the renamer can generate a better error message.
+ | QVARID {% let qname = mkQual tvName (getQVARID $1)
+ in acsa (\cs -> sL1a $1 (HsTyVar (EpAnn (glR $1) [] cs) NotPromoted (sL1n $1 $ qname)))}
-- An inst_type is what occurs in the head of an instance decl
-- e.g. (Foo a, Gaz b) => Wibble a b
diff --git a/compiler/GHC/Rename/Env.hs b/compiler/GHC/Rename/Env.hs
index e9733a8bfc..0c0e944d64 100644
--- a/compiler/GHC/Rename/Env.hs
+++ b/compiler/GHC/Rename/Env.hs
@@ -1097,6 +1097,9 @@ lookup_demoted rdr_name
| otherwise
= star_is_type_hints
; unboundNameX looking_for rdr_name suggestion } }
+ | Just demoted_rdr_name <- demoteRdrNameTv rdr_name,
+ isQual rdr_name
+ = report_qualified_term_in_types rdr_name demoted_rdr_name
| otherwise
= reportUnboundName' (lf_which looking_for) rdr_name
@@ -1104,6 +1107,18 @@ lookup_demoted rdr_name
where
looking_for = LF WL_Constructor WL_Anywhere
+-- Report a qualified variable name in a type signature:
+-- badSig :: Prelude.head
+-- ^^^^^^^^^^^
+report_qualified_term_in_types :: RdrName -> RdrName -> RnM Name
+report_qualified_term_in_types rdr_name demoted_rdr_name =
+ do { mName <- lookupGlobalOccRn_maybe demoted_rdr_name
+ ; case mName of
+ (Just _) -> termNameInType looking_for rdr_name demoted_rdr_name []
+ Nothing -> unboundTermNameInTypes looking_for rdr_name demoted_rdr_name }
+ where
+ looking_for = LF WL_Constructor WL_Global
+
-- If the given RdrName can be promoted to the type level and its promoted variant is in scope,
-- lookup_promoted returns the corresponding type-level Name.
-- Otherwise, the function returns Nothing.
@@ -1152,14 +1167,26 @@ its namespace to DataName and do a second lookup.
The final result (after the renamer) will be:
HsTyVar ("Zero", DataName)
-Another case of demotion happens when the compiler needs to check
+Another case of demotion happens when the user tries to
+use a qualified term at the type level:
+
+ f :: Prelude.id -> Int
+
+This signature passes the parser to be caught by the renamer.
+It allows the compiler to create more informative error messages.
+
+'Prelude.id' in the type signature is parsed as
+ HsTyVar ("id", TvName)
+
+To separate the case of a typo from the case of an
+intentional attempt to use an imported term's name the compiler demotes
+the namespace to VarName (using 'demoteTvNameSpace') and does a lookup.
+
+The same type of demotion happens when the compiler needs to check
if a name of a type variable has already been used for a term that is in scope.
We need to do it to check if a user should change the name
to make his code compatible with the RequiredTypeArguments extension.
-This type of demotion is made via demoteTvNameSpace.
-
-
Note [Promotion]
~~~~~~~~~~~~~~~
When the user mentions a type constructor or a type variable in a
diff --git a/compiler/GHC/Rename/HsType.hs b/compiler/GHC/Rename/HsType.hs
index cb246d1c77..f9720a53e1 100644
--- a/compiler/GHC/Rename/HsType.hs
+++ b/compiler/GHC/Rename/HsType.hs
@@ -2039,7 +2039,7 @@ extract_hs_tv_bndrs_kvs tv_bndrs =
extract_tv :: LocatedN RdrName -> FreeKiTyVars -> FreeKiTyVars
extract_tv tv acc =
- if isRdrTyVar (unLoc tv) then tv:acc else acc
+ if isRdrTyVar (unLoc tv) && (not . isQual) (unLoc tv) then tv:acc else acc
-- Deletes duplicates in a list of Located things. This is used to:
--
diff --git a/compiler/GHC/Rename/Unbound.hs b/compiler/GHC/Rename/Unbound.hs
index d91227670d..c8e77b9e87 100644
--- a/compiler/GHC/Rename/Unbound.hs
+++ b/compiler/GHC/Rename/Unbound.hs
@@ -18,8 +18,11 @@ module GHC.Rename.Unbound
, LookingFor(..)
, unboundName
, unboundNameX
+ , unboundTermNameInTypes
+ , IsTermInTypes(..)
, notInScopeErr
, nameSpacesRelated
+ , termNameInType
)
where
@@ -32,6 +35,7 @@ import GHC.Tc.Errors.Types
import GHC.Tc.Utils.Monad
import GHC.Builtin.Names ( mkUnboundName, isUnboundName, getUnique)
import GHC.Utils.Misc
+import GHC.Utils.Panic (panic)
import GHC.Data.Maybe
import GHC.Data.FastString
@@ -93,6 +97,8 @@ data LookingFor = LF { lf_which :: WhatLooking
, lf_where :: WhereLooking
}
+data IsTermInTypes = UnknownTermInTypes RdrName | TermInTypes RdrName | NoTermInTypes
+
mkUnboundNameRdr :: RdrName -> Name
mkUnboundNameRdr rdr = mkUnboundName (rdrNameOcc rdr)
@@ -107,11 +113,24 @@ unboundName lf rdr = unboundNameX lf rdr []
unboundNameX :: LookingFor -> RdrName -> [GhcHint] -> RnM Name
unboundNameX looking_for rdr_name hints
+ = unboundNameOrTermInType NoTermInTypes looking_for rdr_name hints
+
+unboundTermNameInTypes :: LookingFor -> RdrName -> RdrName -> RnM Name
+unboundTermNameInTypes looking_for rdr_name demoted_rdr_name
+ = unboundNameOrTermInType (UnknownTermInTypes demoted_rdr_name) looking_for rdr_name []
+
+-- Catches imported qualified terms in type signatures
+-- with proper error message and suggestions
+termNameInType :: LookingFor -> RdrName -> RdrName -> [GhcHint] -> RnM Name
+termNameInType looking_for rdr_name demoted_rdr_name external_hints
+ = unboundNameOrTermInType (TermInTypes demoted_rdr_name) looking_for rdr_name external_hints
+
+unboundNameOrTermInType :: IsTermInTypes -> LookingFor -> RdrName -> [GhcHint] -> RnM Name
+unboundNameOrTermInType if_term_in_type looking_for rdr_name hints
= do { dflags <- getDynFlags
; let show_helpful_errors = gopt Opt_HelpfulErrors dflags
- err = notInScopeErr (lf_where looking_for) rdr_name
; if not show_helpful_errors
- then addErr $ TcRnNotInScope err rdr_name [] hints
+ then addErr $ make_error [] hints
else do { local_env <- getLocalRdrEnv
; global_env <- getGlobalRdrEnv
; impInfo <- getImports
@@ -122,9 +141,19 @@ unboundNameX looking_for rdr_name hints
dflags hpt currmod global_env local_env impInfo
rdr_name
; addErr $
- TcRnNotInScope err rdr_name imp_errs (hints ++ suggs) }
+ make_error imp_errs (hints ++ suggs) }
; return (mkUnboundNameRdr rdr_name) }
+ where
+ name_to_search = case if_term_in_type of
+ NoTermInTypes -> rdr_name
+ UnknownTermInTypes demoted_name -> demoted_name
+ TermInTypes demoted_name -> demoted_name
+
+ err = notInScopeErr (lf_where looking_for) name_to_search
+ make_error imp_errs hints = case if_term_in_type of
+ TermInTypes demoted_name -> TcRnTermNameInType demoted_name hints
+ _ -> TcRnNotInScope err name_to_search imp_errs hints
notInScopeErr :: WhereLooking -> RdrName -> NotInScopeError
notInScopeErr where_look rdr_name
@@ -288,7 +317,7 @@ importSuggestions looking_for global_env hpt currMod imports rdr_name
(mod_name, occ_name) = case rdr_name of
Unqual occ_name -> (Nothing, occ_name)
Qual mod_name occ_name -> (Just mod_name, occ_name)
- _ -> error "importSuggestions: dead code"
+ _ -> panic "importSuggestions: dead code"
-- What import statements provide "Mod" at all
diff --git a/compiler/GHC/Tc/Errors/Ppr.hs b/compiler/GHC/Tc/Errors/Ppr.hs
index 2e17295073..984cf95903 100644
--- a/compiler/GHC/Tc/Errors/Ppr.hs
+++ b/compiler/GHC/Tc/Errors/Ppr.hs
@@ -718,6 +718,11 @@ instance Diagnostic TcRnMessage where
TcRnNotInScope err name imp_errs _
-> mkSimpleDecorated $
pprScopeError name err $$ vcat (map ppr imp_errs)
+ TcRnTermNameInType name _
+ -> mkSimpleDecorated $
+ quotes (ppr name) <+>
+ (text "is a term-level binding") $+$
+ (text " and can not be used at the type level.")
TcRnUntickedPromotedThing thing
-> mkSimpleDecorated $
text "Unticked promoted" <+> what
@@ -1475,6 +1480,8 @@ instance Diagnostic TcRnMessage where
-> ErrorWithoutFlag
TcRnNotInScope {}
-> ErrorWithoutFlag
+ TcRnTermNameInType {}
+ -> ErrorWithoutFlag
TcRnUntickedPromotedThing {}
-> WarningWithFlag Opt_WarnUntickedPromotedConstructors
TcRnIllegalBuiltinSyntax {}
@@ -1878,6 +1885,8 @@ instance Diagnostic TcRnMessage where
-> noHints
TcRnNotInScope err _ _ hints
-> scopeErrorHints err ++ hints
+ TcRnTermNameInType _ hints
+ -> hints
TcRnUntickedPromotedThing thing
-> [SuggestAddTick thing]
TcRnIllegalBuiltinSyntax {}
diff --git a/compiler/GHC/Tc/Errors/Types.hs b/compiler/GHC/Tc/Errors/Types.hs
index 13bef7b699..65701f9fee 100644
--- a/compiler/GHC/Tc/Errors/Types.hs
+++ b/compiler/GHC/Tc/Errors/Types.hs
@@ -1717,6 +1717,7 @@ data TcRnMessage where
-> [ImportError] -- ^ import errors that are relevant
-> [GhcHint] -- ^ hints, e.g. enable DataKinds to refer to a promoted data constructor
-> TcRnMessage
+ TcRnTermNameInType :: RdrName -> [GhcHint] -> TcRnMessage
{-| TcRnUntickedPromotedThing is a warning (controlled with -Wunticked-promoted-constructors)
that is triggered by an unticked occurrence of a promoted data constructor.
diff --git a/compiler/GHC/Types/Error/Codes.hs b/compiler/GHC/Types/Error/Codes.hs
index 3b7220f703..544ebc905f 100644
--- a/compiler/GHC/Types/Error/Codes.hs
+++ b/compiler/GHC/Types/Error/Codes.hs
@@ -432,6 +432,7 @@ type family GhcDiagnosticCode c = n | n -> c where
GhcDiagnosticCode "TcRnSectionWithoutParentheses" = 95880
GhcDiagnosticCode "TcRnIllegalImplicitParameterBindings" = 50730
GhcDiagnosticCode "TcRnIllegalTupleSection" = 59155
+ GhcDiagnosticCode "TcRnTermNameInType" = 37479
GhcDiagnosticCode "TcRnUntickedPromotedThing" = 49957
GhcDiagnosticCode "TcRnIllegalBuiltinSyntax" = 39716
diff --git a/testsuite/tests/rename/should_fail/T21605a.hs b/testsuite/tests/rename/should_fail/T21605a.hs
new file mode 100644
index 0000000000..afbcdd8ce5
--- /dev/null
+++ b/testsuite/tests/rename/should_fail/T21605a.hs
@@ -0,0 +1,6 @@
+module T21605a where
+
+import Prelude
+
+wrongSig :: Prelude.true
+wrongSig = undefined
diff --git a/testsuite/tests/rename/should_fail/T21605a.stderr b/testsuite/tests/rename/should_fail/T21605a.stderr
new file mode 100644
index 0000000000..7be47098df
--- /dev/null
+++ b/testsuite/tests/rename/should_fail/T21605a.stderr
@@ -0,0 +1,10 @@
+
+T21605a.hs:5:13: error: [GHC-76037]
+ Not in scope: ‘Prelude.true’
+ NB: the module ‘Prelude’ does not export ‘true’.
+ Suggested fix:
+ Perhaps use one of these:
+ type constructor or class ‘Prelude.Num’ (imported from Prelude),
+ type constructor or class ‘Prelude.Ord’ (imported from Prelude),
+ type constructor or class ‘Prelude.Enum’ (imported from Prelude)
+
diff --git a/testsuite/tests/rename/should_fail/T21605b.hs b/testsuite/tests/rename/should_fail/T21605b.hs
new file mode 100644
index 0000000000..fc37a01502
--- /dev/null
+++ b/testsuite/tests/rename/should_fail/T21605b.hs
@@ -0,0 +1,7 @@
+{-# LANGUAGE DataKinds #-}
+module T21605b where
+
+import Prelude
+
+wrongSig :: Prelude.true
+wrongSig = undefined
diff --git a/testsuite/tests/rename/should_fail/T21605b.stderr b/testsuite/tests/rename/should_fail/T21605b.stderr
new file mode 100644
index 0000000000..ebb74bad48
--- /dev/null
+++ b/testsuite/tests/rename/should_fail/T21605b.stderr
@@ -0,0 +1,9 @@
+
+T21605b.hs:6:13: error: [GHC-76037]
+ Not in scope: ‘Prelude.true’
+ NB: the module ‘Prelude’ does not export ‘true’.
+ Suggested fix:
+ Perhaps use one of these:
+ data constructor ‘Prelude.True’ (imported from Prelude),
+ type constructor or class ‘Prelude.Num’ (imported from Prelude),
+ type constructor or class ‘Prelude.Ord’ (imported from Prelude)
diff --git a/testsuite/tests/rename/should_fail/T21605c.hs b/testsuite/tests/rename/should_fail/T21605c.hs
new file mode 100644
index 0000000000..d3485e0957
--- /dev/null
+++ b/testsuite/tests/rename/should_fail/T21605c.hs
@@ -0,0 +1,6 @@
+module T21605b where
+
+import Prelude
+
+wrongSig :: Prelude.head
+wrongSig = undefined
diff --git a/testsuite/tests/rename/should_fail/T21605c.stderr b/testsuite/tests/rename/should_fail/T21605c.stderr
new file mode 100644
index 0000000000..e14c20ab0a
--- /dev/null
+++ b/testsuite/tests/rename/should_fail/T21605c.stderr
@@ -0,0 +1,9 @@
+
+T21605c.hs:5:13: error: [GHC-37479]
+ ‘Prelude.head’ is a term-level binding
+ and can not be used at the type level.
+ Suggested fix:
+ Perhaps use one of these:
+ type constructor or class ‘Prelude.Read’ (imported from Prelude),
+ type constructor or class ‘Prelude.Real’ (imported from Prelude),
+ type constructor or class ‘Prelude.ReadS’ (imported from Prelude)
diff --git a/testsuite/tests/rename/should_fail/T21605d.hs b/testsuite/tests/rename/should_fail/T21605d.hs
new file mode 100644
index 0000000000..f3a98cf925
--- /dev/null
+++ b/testsuite/tests/rename/should_fail/T21605d.hs
@@ -0,0 +1,3 @@
+module T21605d where
+
+f (x :: Prelude.id) = x \ No newline at end of file
diff --git a/testsuite/tests/rename/should_fail/T21605d.stderr b/testsuite/tests/rename/should_fail/T21605d.stderr
new file mode 100644
index 0000000000..3db644aa93
--- /dev/null
+++ b/testsuite/tests/rename/should_fail/T21605d.stderr
@@ -0,0 +1,8 @@
+T21605d.hs:3:9: [GHC-37479]
+ ‘Prelude.id’ is a term-level binding
+ and can not be used at the type level.
+ Suggested fix:
+ Perhaps use one of these:
+ type constructor or class ‘Prelude.Eq’ (imported from Prelude),
+ type constructor or class ‘Prelude.IO’ (imported from Prelude),
+ type constructor or class ‘Prelude.Ord’ (imported from Prelude) \ No newline at end of file
diff --git a/testsuite/tests/rename/should_fail/all.T b/testsuite/tests/rename/should_fail/all.T
index 43bff14df1..f4f6685fdc 100644
--- a/testsuite/tests/rename/should_fail/all.T
+++ b/testsuite/tests/rename/should_fail/all.T
@@ -177,6 +177,9 @@ test('T19843m', normal, compile_fail, [''])
test('T11167_ambig', normal, compile_fail, [''])
test('T18138', normal, compile_fail, [''])
test('T20147', normal, compile_fail, [''])
-
test('RnEmptyStatementGroup1', normal, compile_fail, [''])
test('RnImplicitBindInMdoNotation', normal, compile_fail, [''])
+test('T21605a', normal, compile_fail, [''])
+test('T21605b', normal, compile_fail, [''])
+test('T21605c', normal, compile_fail, [''])
+test('T21605d', normal, compile_fail, [''])