Code generation and delegation to nested routes#1887
Code generation and delegation to nested routes#1887parsonsmatt wants to merge 140 commits intomasterfrom
Conversation
Co-authored-by: Matt Parsons <[email protected]>
…ontent Show truncated content in handler content
…s-generation Mattp/control resources generation
|
Hi Matt! @jezen asked me to test this out on our codebase, and from what I can tell there is some kind of error when we use mkYesodSubData. Here's an example excerpt: mkYesodSubData "(SubsiteClass subsite master) => SubsiteData subsite"
$(makeRelativeToProject "config/routes.yesodroutes" >>= parseRoutesFile)results in many errors of the format: I can't immediately spot why in your PR. |
|
Great, thank you for the report! I don't think we have a test case for that in the repository yet. I'll make one and see if I can get it fixed. |
…sod into mattp/nested-route-discovery
|
@L0neGamer I added a test case and fixed, mind trying again? |
|
Getting another compile time issue, this time with Here are some more snippets: results in the error: It seems that you're not passing the subsite variable when doing the dispatch? |
|
Great! Thanks. The more of a reproducer you can give me the faster I can fix it up 😄 |
|
I'll have a look shortly if this is insufficient. |
When nested route types have type parameters (e.g. from parameterized subsites), isInstance requires fully-applied types. Added fullyApplyType helper that looks up type arity and applies fresh type variables. Fixed all isInstance checks across Dispatch, RenderRoute, ParseRoute, and RouteAttrs TH modules. This fixes the "Expecting one more argument to 'RouteR'" error when using mkYesodSubDispatch with parameterized subsites that have nested routes. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
Please observe PR here for failing test example: #1910 |
Tests that mkYesodSubDispatch compiles when the subsite has constrained type parameters (e.g. ParamSubDispatchClass subsite master => ParamSubDispatch subsite). This covers the isInstance / fullyApplyType fix from the previous commit. Note: mkYesodSubDispatch + nested route delegation (`:` syntax) is not yet supported for subsites because yesodDispatchNested expects YesodRunnerEnv but subsite dispatch uses YesodSubRunnerEnv. The ParameterizedSubData module already covers mkYesodSubData with nested routes. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
I believe Claude's suggested test does not demonstrate the error while my commit does; feel free to cherry pick and rejig mine as you see fit. |
…ed routes When a subsite has nested routes (`:` syntax), mkYesodSubDispatch now handles them via two mechanisms: 1. Inline dispatch (backward-compatible): When no nested dispatch instance exists, mkDispatchClause's `go` for ResourceParent recursively generates child dispatch clauses inline. Existing user code using mkYesodSubDispatch works with nested routes without changes. 2. Class-based delegation: New YesodSubDispatchNested class (parallel to YesodDispatchNested) enables module separation for subsite nested routes. mkYesodSubDispatchInstance generates both YesodSubDispatch and YesodSubDispatchNested instances in one splice. Also incorporates l0neGamer's PR #1910 test case with associated types, ConstraintKinds, and dynamic pieces in nested route parents. Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Replace [t| ParentSite $(pure x) |] TH splices with manual type construction (ConT ''ParentSite `AppT` x) to avoid $ being parsed as a type operator on GHC < 9.4 (lts-16, lts-18) - Add data-default to tests suite build-depends since hs-source-dirs includes src/ and Dispatch.hs conditionally imports Data.Default when wai-extra < 3.1.14 (lts-20) Co-Authored-By: Claude Opus 4.6 <[email protected]>
Replace foldMap with mapM + mconcat since Monoid (Q (a, b)) isn't available on older GHCs that lack Monoid a => Monoid (Q a). Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Replace Quote-based polymorphism with explicit liftQ in generateParseRouteClause (Quote class doesn't exist pre-GHC 9.0) - Add CPP guards for ConP arity change (template-haskell 2.18/GHC 9.4) and TupE Maybe wrapping (template-haskell 2.16/GHC 8.10) in tests Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
OK, I think we have incorporated your test case- mind giving it another go? Thanks for your patience 🙏🏻 |
Use TH quoted name 'W.pathInfo instead of mkName "pathInfo" in record update expression within genNestedDispatchClauses. The mkName version creates an unqualified name that requires Network.Wai to be imported at the splice site. Fixes error reported by l0negamer: "Not in scope: record field 'pathInfo'" when using mkYesodDispatchOpts with nested routes. Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Replace mkName "renderRoute" with 'renderRoute in RenderRoute.hs for consistency with 'renderRouteNested usage - Replace mkName lambda-bound variables (sroute, p, r) with newName in Dispatch.hs to avoid potential name capture Co-Authored-By: Claude Opus 4.6 <[email protected]>
Introduces mkLambda in Internal.hs that generates a single-argument lambda with a fresh name, replacing manual newName + LamE patterns across Dispatch.hs for better name hygiene and less boilerplate. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
Can you clarify what you mean? ie that each use of |
|
So, the changes you make add the type arguments to the type of the subroutes, but that is a breaking change if any of the subroutes are used in a type signature. For example if we take the following example from before: -- routes.yesodroutes
!/#{AssociatedType subsite}/route RouteR':
/ RouteR GET POST DELETEThen we will get the following types and constructors: I hope you can see the change; the type |
* Initial commit * Add basic tests for parent attr inclusion * Minimize language extensions * Add since annotation based on directions * Add Lift instance for Set from th-lift-instances * Add th-lift-instances * Add th-lift-instances to test targets * Use record to represent parent details over tuple * Add changelog entry * Update ChangeLog

Nested Route Separation
We have almost 2,000 total routes in our codebase now, with 1,100 of them on the top-level.
We have been leveraging nested routes to organize the routes and make certain things easier - namely, reducing the overall size of the
Route WebAppwhich makes pattern matching more efficient in compilation.However, compiling
mkYesodDatafor our application is a large bottleneck.Compilation takes a significant amount of time and much work must be redone, even when most of the routing information could have been saved.
One thing that would significantly help is the ability to separate out the sub-route datatypes, generate the instance for these separately, and refer to them elsewhere.
Another thing that would help is having a finer grained
YesodDispatchfacility - allowing us to, in tests, specify more precisely which parts of the route structure we actually need to deal with.Currently, we need almost 7k modules in order to compile a test that refers to
YesodDispatch, since this brings in the transitive dependencies of every web handler.With a finer grained
YesodDispatch, able to refer to route fragments, we'd be able to avoid depending on more of the website than we really need to.We expect this to yield significant benefits when we move more testing infrastructure to buck2 - right now, changing anything used by any part of the web app would require running all tests that exercise anything in the webapp!
With more granular
YesodDispatch, we gain the ability to only run tests on parts of the app that actually use the route structure in question.This PR is a breaking change, and so will be
yesod-core-0.2.0.0. When I tried this branch on our package, it required 0 modifications to upgrade - most of the breaking changes here are breaking "advanced power user" type functions, like the ability to customize theMkDispatchSettingsto have highly custom route behaviors. We don't use any of those, and I have no idea how widespread their use is. I will admit to finding them very difficult to use and adapt, which is part of why I ended up scrapping their use in theParseRoutegeneration (the class isn't even used).For the most part, though, all prior behavior remains unchanged. The performance of routing may be slightly worse -
YesodDispatchNestedreturns aMaybe Application(but with theRequestpulled out - only the "send response" side is in the Maybe) to support fallthrough. And if you enable fallthrough, then the app will do more checking (proportional to the number of routes and nested routes) rather than early-404ing. But IMO for perf, we're better off coming up with a trie-based router than trying to optimize this one much more.For an example of the testing facilities this provides, see parsonsmatt/hspec-yesod#7 - particularly, this file has an introduction to how the tests are separated and defined.
Fix for #1880
Before submitting your PR, check that you've:
@sincedeclarations to the Haddocks for new, public APIsAfter submitting your PR: