[Git][ghc/ghc][wip/sand-witch/27423-gadt-parens] Parentheses in prefix GADT constructors (#27423)
Andrei Borzenkov pushed to branch wip/sand-witch/27423-gadt-parens at Glasgow Haskell Compiler / GHC Commits: 3a32ebde by Andrei Borzenkov at 2026-07-10T11:45:31+04:00 Parentheses in prefix GADT constructors (#27423) Updated `splitLHsGadtTy` to allow looking through the parentheses for inner binders. General example of a code pattern that's allowed now: data S a where MkS :: (forall a. S a) That should work now with any combination of nested foralls and parentheses. We don't perform parenthesis unwrapping for record GADT constructors in accordance with GHC Proposal #402. Updated `pprConDecl` to improve the `parse == parse . ppr . parse` property of GADT pretty-printing. The pretty printer can now output code that's similar to this: data T a where MkT1 :: (forall a. T a) MkT2 :: forall . forall a. T a These are special cases of inner forall binders for prefix GADT constructors, when we have either implicit or zero explicit outer binders. - - - - - 13 changed files: - + changelog.d/allow-gadt-prefx-con-parens - compiler/GHC/Hs/Decls.hs - compiler/GHC/Hs/Type.hs - − testsuite/tests/gadt/T14320.stderr - testsuite/tests/gadt/T18191.hs - testsuite/tests/gadt/T18191.stderr - + testsuite/tests/gadt/T27423a.hs - + testsuite/tests/gadt/T27423b.hs - + testsuite/tests/gadt/T27423b.stderr - testsuite/tests/gadt/all.T - testsuite/tests/printer/Makefile - + testsuite/tests/printer/T27423c.hs - testsuite/tests/printer/all.T Changes: ===================================== changelog.d/allow-gadt-prefx-con-parens ===================================== @@ -0,0 +1,16 @@ +section: language +synopsis: Allow parentheses in prefix GADT constructor declarations, as specified + by GHC Proposal #402 "Stable GADT constructor syntax". +issues: #27423 +mrs: !16321 + +description: { + Parenthesized types are now accepted in prefix GADT constructor declarations, + even when they contain explicit ``forall`` quantifiers. For example: + + data T where + MkT :: (forall a. a -> b -> T) + + This is equivalent to ``MkT :: forall {b}. (forall a. a -> b -> T)``, so the + forall-or-nothing rule continues to be respected. +} ===================================== compiler/GHC/Hs/Decls.hs ===================================== @@ -910,13 +910,28 @@ pprConDecl (ConDeclGADT { con_names = cons , con_res_ty = res_ty, con_modifiers = mods, con_doc = doc }) = pprMaybeWithDoc doc $ pprLHsModifiers mods <+> ppr_con_names (toList cons) <+> dcolon <+> (sep [pprHsOuterSigTyVarBndrs outer_bndrs + <+> inner_bndrs_lguard <+> hsep (map pprHsForAllTelescope inner_bndrs) <+> pprLHsContext mcxt, - sep (ppr_args args ++ [ppr res_ty]) ]) + sep (ppr_args args ++ [ppr res_ty]), + inner_bndrs_rguard ]) where ppr_args (PrefixConGADT _ args) = map (pprHsConDeclFieldWith (\arr tyDoc -> tyDoc <+> pprHsModifiedFunArr arr)) args ppr_args (RecConGADT _ fields) = [pprHsConDeclRecFields (unLoc fields) <+> arrow] + -- We may have inner binders without outer ones, for example: + -- data D a where MkD :: forall. forall a. D a + -- and + -- data D a where MkD :: (forall a. D a) + -- These cases have different outer_bndrs: explicit or implicit. + -- We want to add an empty `forall.` or wrap type into parentheses + -- to highlight the fact that binds in the question are indeed inner. + (inner_bndrs_lguard, inner_bndrs_rguard) + | null inner_bndrs = (empty, empty) + | HsOuterImplicit{} <- outer_bndrs = (lparen, rparen) + | HsOuterExplicit{hso_bndrs=[]} <- outer_bndrs = (text "forall" <+> text ".", empty) + | otherwise = (empty, empty) + ppr_con_names :: (OutputableBndr a) => [GenLocated l a] -> SDoc ppr_con_names = pprWithCommas (pprPrefixOcc . unLoc) ===================================== compiler/GHC/Hs/Type.hs ===================================== @@ -898,12 +898,11 @@ splitLHsGadtTy (L _ sig_ty) | (outer_bndrs, sigma_ty) <- split_outer_bndrs sig_ty , (inner_bndrs, phi_ty) <- split_inner_bndrs sigma_ty , (mb_ctxt, rho_ty) <- splitLHsQualTy_KP phi_ty - = case rho_ty of - L _ (HsFunTy _ _ (L _ (XHsType HsRecTy{})) _) | not (null inner_bndrs) + = if is_gadt_rec_ty rho_ty && not (null inner_bndrs) -- Bad! Record GADTs are not allowed to have inner_bndrs, -- undo the split to get a proper error message later - -> (outer_bndrs, [], Nothing, sigma_ty) - _ -> (outer_bndrs, inner_bndrs, mb_ctxt, rho_ty) + then (outer_bndrs, [], Nothing, sigma_ty) + else (outer_bndrs, inner_bndrs, mb_ctxt, rho_ty) where split_outer_bndrs :: HsSigType GhcPs -> (HsOuterSigTyVarBndrs GhcPs, LHsType GhcPs) split_outer_bndrs (HsSig{sig_bndrs = outer_bndrs, sig_body = body_ty}) = @@ -914,8 +913,17 @@ splitLHsGadtTy (L _ sig_ty) , hst_body = body }) = let ~(teles, t) = split_inner_bndrs body in (tele:teles, t) + split_inner_bndrs t@(L _ (HsParTy _ ty)) + | not (is_gadt_rec_ty ty) + = split_inner_bndrs ty + | otherwise + = ([], t) split_inner_bndrs t = ([], t) + -- type of form {fld :: ty, ...} -> ResTy + is_gadt_rec_ty (L _ (HsFunTy _ _ (L _ (XHsType HsRecTy{})) _)) = True + is_gadt_rec_ty _ = False + -- | Decompose a type of the form @forall <tvs>. body@ into its constituent -- parts. Only splits type variable binders that -- were quantified invisibly (e.g., @forall a.@, with a dot). ===================================== testsuite/tests/gadt/T14320.stderr deleted ===================================== @@ -1,4 +0,0 @@ - -T14320.hs:17:14: error: [GHC-71492] - GADT constructor type signature cannot contain nested ‘forall’s or contexts - In the definition of data constructor ‘TEBad’ ===================================== testsuite/tests/gadt/T18191.hs ===================================== @@ -2,15 +2,6 @@ {-# LANGUAGE RankNTypes #-} module T18191 where -data T where - MkT :: (forall a. a -> b -> T) - -data S a where - MkS :: (forall a. S a) - -data U a where - MkU :: (Show a => U a) - data Z a where MkZ1 :: forall a. forall b. { unZ1 :: (a, b) } -> Z (a, b) MkZ2 :: Eq a => Eq b => { unZ1 :: (a, b) } -> Z (a, b) ===================================== testsuite/tests/gadt/T18191.stderr ===================================== @@ -1,28 +1,16 @@ - -T18191.hs:6:11: error: [GHC-71492] - • GADT constructor type signature cannot contain nested ‘forall’s or contexts - • In the definition of data constructor ‘MkT’ - -T18191.hs:9:11: error: [GHC-71492] - • GADT constructor type signature cannot contain nested ‘forall’s or contexts - • In the definition of data constructor ‘MkS’ - -T18191.hs:12:11: error: [GHC-71492] - • GADT constructor type signature cannot contain nested ‘forall’s or contexts - • In the definition of data constructor ‘MkU’ - -T18191.hs:15:21: error: [GHC-71492] +T18191.hs:6:21: error: [GHC-71492] • GADT constructor type signature cannot contain nested ‘forall’s or contexts • In the definition of data constructor ‘MkZ1’ -T18191.hs:15:31: error: [GHC-89246] +T18191.hs:6:31: error: [GHC-89246] • Record syntax is illegal here: {unZ1 :: (a, b)} • In the definition of data constructor ‘MkZ1’ -T18191.hs:16:19: error: [GHC-71492] +T18191.hs:7:19: error: [GHC-71492] • GADT constructor type signature cannot contain nested ‘forall’s or contexts • In the definition of data constructor ‘MkZ2’ -T18191.hs:16:27: error: [GHC-89246] +T18191.hs:7:27: error: [GHC-89246] • Record syntax is illegal here: {unZ1 :: (a, b)} • In the definition of data constructor ‘MkZ2’ + ===================================== testsuite/tests/gadt/T27423a.hs ===================================== @@ -0,0 +1,31 @@ +{-# LANGUAGE GADTs #-} +module T27423a where + +data G a where + MkG1 :: a -> G a + MkG2 :: (a -> G a) + MkG3 :: forall a. a -> G a + MkG4 :: forall a. (a -> G a) + +-- this is equivalent to `forall {b}. (forall a. a -> b -> T)`. +data T where + MkT2 :: (forall a. a -> b -> T) + +-- this is equivalent to `forall. (forall a. S a)`. +data S a where + MkS :: (forall a. S a) + +-- A forall and a context combined inside the same parentheses, with no +-- outer forall at all. +data Y a where + MkY :: (forall a. Show a => a -> Y a) + +-- Multiple, redundant nested parentheses around the whole type should +-- be accepted. +data H a where + MkH :: ((a -> H a)) + +-- An unparenthesised outer forall together with an independent, +-- parenthesised inner forall. +data I a where + MkI :: forall a. (forall b. I (b, a)) ===================================== testsuite/tests/gadt/T27423b.hs ===================================== @@ -0,0 +1,16 @@ +{-# LANGUAGE GADTs #-} +module T27423b where + +-- Record-style GADT constructors must remain unparenthesisable, per +-- GHC Proposal #402: this is out of scope for #27423 and should +-- continue to be rejected. +data T1 a where + MkT1 :: ({ fld :: a } -> T1 a) + +data T2 a where + MkT2 :: (forall a. { fld :: a } -> T2 a) + +-- Without parentheses, forall-or-nothing applies to the whole type, so +-- `b` is not implicitly quantified and this must be rejected. +data T3 where + MkT3 :: forall a. a -> b -> T3 ===================================== testsuite/tests/gadt/T27423b.stderr ===================================== @@ -0,0 +1,15 @@ +T27423b.hs:8:12: error: [GHC-89246] + • Record syntax is illegal here: {fld :: a} + • In the definition of data constructor ‘MkT1’ + +T27423b.hs:11:12: error: [GHC-71492] + • GADT constructor type signature cannot contain nested ‘forall’s or contexts + • In the definition of data constructor ‘MkT2’ + +T27423b.hs:11:22: error: [GHC-89246] + • Record syntax is illegal here: {fld :: a} + • In the definition of data constructor ‘MkT2’ + +T27423b.hs:16:26: error: [GHC-76037] + Not in scope: type variable ‘b’ + ===================================== testsuite/tests/gadt/all.T ===================================== @@ -114,7 +114,7 @@ test('T7558', normal, compile, ['']) test('T9380', normal, compile_and_run, ['']) test('T12087', normal, compile_fail, ['']) test('T12468', normal, compile_fail, ['']) -test('T14320', normal, compile_fail, ['']) +test('T14320', normal, compile, ['']) test('T14719', normal, compile_fail, ['-fdiagnostics-show-caret']) test('T14808', normal, compile, ['']) test('T15009', normal, compile, ['']) @@ -132,3 +132,6 @@ test('T19847b', normal, compile, ['']) test('T23022', normal, compile, ['-dcore-lint']) test('T23023', normal, compile_fail, ['-O -dcore-lint']) # todo: move this test? test('T23298', normal, compile_fail, ['']) + +test('T27423a', normal, compile, ['']) +test('T27423b', normal, compile_fail, ['']) ===================================== testsuite/tests/printer/Makefile ===================================== @@ -932,3 +932,8 @@ PprModifiers: PprQualifiedStrings: $(CHECK_PPR) $(LIBDIR) PprQualifiedStrings.hs $(CHECK_EXACT) $(LIBDIR) PprQualifiedStrings.hs + +.PHONY: T27423c +T27423c: + $(CHECK_PPR) $(LIBDIR) T27423c.hs + $(CHECK_EXACT) $(LIBDIR) T27423c.hs ===================================== testsuite/tests/printer/T27423c.hs ===================================== @@ -0,0 +1,26 @@ +{-# LANGUAGE GADTs #-} +module T27423c where + +-- Exact-printing regression test +-- Not every declaration there would pass renamer without errors +data G a where + MkG1 :: a -> G a + MkG2 :: (a -> G a) + MkG3 :: forall a. a -> G a + MkG4 :: forall a. (a -> G a) + +data T where + MkT1 :: forall a. a -> b -> T + MkT2 :: (forall a. a -> b -> T) + +data S a where + MkS :: (forall a. S a) + +data U a where + MkU :: (Show a => U a) + +data V a where + MkV1 :: ((a -> V a)) + MkV2 :: forall a. (forall b. V (b, a)) + MkV3 :: (forall a. Show a => a -> V a) + MkV4 :: forall a. ((forall b. (Show a => a -> (b -> V a)))) ===================================== testsuite/tests/printer/all.T ===================================== @@ -223,3 +223,5 @@ test('TestLevelImports', [ignore_stderr, req_ppr_deps], makefile_test, ['TestLev test('TestNamedDefaults', [ignore_stderr, req_ppr_deps], makefile_test, ['TestNamedDefaults']) test('PprModifiers', [ignore_stderr,req_ppr_deps], makefile_test, ['PprModifiers']) test('PprQualifiedStrings', [ignore_stderr,req_ppr_deps], makefile_test, ['PprQualifiedStrings']) + +test('T27423c', [ignore_stderr,req_ppr_deps], makefile_test, ['T27423c']) View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/3a32ebdea1a458a0a606f59a5404673a... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/3a32ebdea1a458a0a606f59a5404673a... You're receiving this email because of your account on gitlab.haskell.org.
participants (1)
-
Andrei Borzenkov (@sand-witch)