Merge pull request #1697 from d86leader/master

Generate appropriate Handler and Widget synonyms for polymorphic sites
This commit is contained in:
Michael Snoyman 2020-09-22 17:30:26 +03:00 committed by GitHub
commit 1b79db382d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 143 additions and 6 deletions

View File

@ -1,5 +1,9 @@
# ChangeLog for yesod-core # ChangeLog for yesod-core
## 1.6.18.4
* Fixed a bug where `mkYesod` and other TH functions didn't work for datatypes with explicitly stated type variables, including the case with typeclass constraints. [https://github.com/yesodweb/yesod/pull/1697](#1697)
## 1.6.18.3 ## 1.6.18.3
* Remove mention of an oudated Yesod type (`GHandler`) from the docs for `handlerToIO`. [https://github.com/yesodweb/yesod/pull/1695](#1695) * Remove mention of an oudated Yesod type (`GHandler`) from the docs for `handlerToIO`. [https://github.com/yesodweb/yesod/pull/1695](#1695)

View File

@ -141,9 +141,12 @@ mkYesodGeneral appCxt' namestr mtys isSub f resS = do
let name = mkName namestr let name = mkName namestr
-- Generate as many variable names as the arity indicates -- Generate as many variable names as the arity indicates
vns <- replicateM (arity - length mtys) $ newName "t" vns <- replicateM (arity - length mtys) $ newName "t"
-- Base type (site type with variables) -- types that you apply to get a concrete site name
let argtypes = fmap nameToType mtys ++ fmap VarT vns let argtypes = fmap nameToType mtys ++ fmap VarT vns
site = foldl' AppT (ConT name) argtypes -- typevars that should appear in synonym head
let argvars = (fmap mkName . filter isTvar) mtys ++ vns
-- Base type (site type with variables)
let site = foldl' AppT (ConT name) argtypes
res = map (fmap (parseType . dropBracket)) resS res = map (fmap (parseType . dropBracket)) resS
renderRouteDec <- mkRenderRouteInstance appCxt site res renderRouteDec <- mkRenderRouteInstance appCxt site res
routeAttrsDec <- mkRouteAttrsInstance appCxt site res routeAttrsDec <- mkRouteAttrsInstance appCxt site res
@ -160,7 +163,7 @@ mkYesodGeneral appCxt' namestr mtys isSub f resS = do
, renderRouteDec , renderRouteDec
, [routeAttrsDec] , [routeAttrsDec]
, resourcesDec , resourcesDec
, if isSub then [] else masterTypeSyns vns site , if isSub then [] else masterTypeSyns argvars site
] ]
return (dataDec, dispatchDec) return (dataDec, dispatchDec)

View File

@ -11,6 +11,7 @@ module Yesod.Routes.Parse
, TypeTree (..) , TypeTree (..)
, dropBracket , dropBracket
, nameToType , nameToType
, isTvar
) where ) where
import Language.Haskell.TH.Syntax import Language.Haskell.TH.Syntax
@ -264,8 +265,13 @@ ttToType (TTApp x y) = ttToType x `AppT` ttToType y
ttToType (TTList t) = ListT `AppT` ttToType t ttToType (TTList t) = ListT `AppT` ttToType t
nameToType :: String -> Type nameToType :: String -> Type
nameToType t@(h:_) | isLower h = VarT $ mkName t nameToType t = if isTvar t
nameToType t = ConT $ mkName t then VarT $ mkName t
else ConT $ mkName t
isTvar :: String -> Bool
isTvar (h:_) = isLower h
isTvar _ = False
pieceFromString :: String -> Either (CheckOverlap, String) (CheckOverlap, Piece String) pieceFromString :: String -> Either (CheckOverlap, String) (CheckOverlap, Piece String)
pieceFromString ('#':'!':x) = Right $ (False, Dynamic $ dropBracket x) pieceFromString ('#':'!':x) = Right $ (False, Dynamic $ dropBracket x)

View File

@ -11,6 +11,7 @@ import YesodCoreTest.NoOverloadedStrings
import YesodCoreTest.InternalRequest import YesodCoreTest.InternalRequest
import YesodCoreTest.ErrorHandling import YesodCoreTest.ErrorHandling
import YesodCoreTest.Cache import YesodCoreTest.Cache
import YesodCoreTest.ParameterizedSite
import qualified YesodCoreTest.WaiSubsite as WaiSubsite import qualified YesodCoreTest.WaiSubsite as WaiSubsite
import qualified YesodCoreTest.Redirect as Redirect import qualified YesodCoreTest.Redirect as Redirect
import qualified YesodCoreTest.JsLoader as JsLoader import qualified YesodCoreTest.JsLoader as JsLoader
@ -43,6 +44,7 @@ specs = do
internalRequestTest internalRequestTest
errorHandlingTest errorHandlingTest
cacheTest cacheTest
parameterizedSiteTest
WaiSubsite.specs WaiSubsite.specs
Redirect.specs Redirect.specs
JsLoader.specs JsLoader.specs

View File

@ -0,0 +1,37 @@
{-# LANGUAGE OverloadedStrings #-}
module YesodCoreTest.ParameterizedSite
( parameterizedSiteTest
) where
import Data.ByteString.Lazy (ByteString)
import Network.Wai.Test (runSession, request, defaultRequest, assertBodyContains)
import Test.Hspec (Spec, describe, it)
import Yesod.Core (YesodDispatch)
import Yesod.Core.Dispatch (toWaiApp)
import YesodCoreTest.ParameterizedSite.PolyAny (PolyAny (..))
import YesodCoreTest.ParameterizedSite.PolyShow (PolyShow (..))
import YesodCoreTest.ParameterizedSite.Compat (Compat (..))
-- These are actually tests for template haskell. So if it compiles, it works
parameterizedSiteTest :: Spec
parameterizedSiteTest = describe "Polymorphic Yesod sites" $ do
it "Polymorphic unconstrained stub" $ runStub (PolyAny ())
it "Polymorphic stub with Show" $ runStub' "1337" (PolyShow 1337)
it "Polymorphic unconstrained stub, old-style" $ runStub (Compat () ())
runStub :: YesodDispatch a => a -> IO ()
runStub stub =
let actions = do
res <- request defaultRequest
assertBodyContains "Stub" res
in toWaiApp stub >>= runSession actions
runStub' :: YesodDispatch a => ByteString -> a -> IO ()
runStub' body stub =
let actions = do
res <- request defaultRequest
assertBodyContains "Stub" res
assertBodyContains body res
in toWaiApp stub >>= runSession actions

View File

@ -0,0 +1,27 @@
{-# LANGUAGE
TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses
, OverloadedStrings, StandaloneDeriving, FlexibleInstances
#-}
module YesodCoreTest.ParameterizedSite.Compat
( Compat (..)
) where
import Yesod.Core
-- | Parameterized without constraints, and we call mkYesod without type vars,
-- like people used to do before the last 3 commits
data Compat a b = Compat a b
mkYesod "Compat" [parseRoutes|
/ HomeR GET
|]
instance Yesod (Compat a b)
getHomeR :: Handler a b Html
getHomeR = defaultLayout
[whamlet|
<p>
Stub
|]

View File

@ -0,0 +1,26 @@
{-# LANGUAGE
TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses
, OverloadedStrings, StandaloneDeriving, FlexibleInstances
#-}
module YesodCoreTest.ParameterizedSite.PolyAny
( PolyAny (..)
) where
import Yesod.Core
-- | Parameterized without constraints
data PolyAny a = PolyAny a
mkYesod "PolyAny a" [parseRoutes|
/ HomeR GET
|]
instance Yesod (PolyAny a)
getHomeR :: Handler a Html
getHomeR = defaultLayout
[whamlet|
<p>
Stub
|]

View File

@ -0,0 +1,28 @@
{-# LANGUAGE
TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses
, OverloadedStrings, StandaloneDeriving, FlexibleInstances
#-}
module YesodCoreTest.ParameterizedSite.PolyShow
( PolyShow (..)
) where
import Yesod.Core
-- | Parameterized with 'Show' constraint
data PolyShow a = PolyShow a
mkYesod "(Show a) => PolyShow a" [parseRoutes|
/ HomeR GET
|]
instance Show a => Yesod (PolyShow a)
getHomeR :: Show a => Handler a Html
getHomeR = do
PolyShow x <- getYesod
defaultLayout
[whamlet|
<p>
Stub #{show x}
|]

View File

@ -1,5 +1,5 @@
name: yesod-core name: yesod-core
version: 1.6.18.3 version: 1.6.18.4
license: MIT license: MIT
license-file: LICENSE license-file: LICENSE
author: Michael Snoyman <michael@snoyman.com> author: Michael Snoyman <michael@snoyman.com>
@ -156,6 +156,10 @@ test-suite tests
YesodCoreTest.MediaData YesodCoreTest.MediaData
YesodCoreTest.NoOverloadedStrings YesodCoreTest.NoOverloadedStrings
YesodCoreTest.NoOverloadedStringsSub YesodCoreTest.NoOverloadedStringsSub
YesodCoreTest.ParameterizedSite
YesodCoreTest.ParameterizedSite.Compat
YesodCoreTest.ParameterizedSite.PolyAny
YesodCoreTest.ParameterizedSite.PolyShow
YesodCoreTest.RawResponse YesodCoreTest.RawResponse
YesodCoreTest.Redirect YesodCoreTest.Redirect
YesodCoreTest.Reps YesodCoreTest.Reps