Description
A promising suggestion by @MilesCranmer (part of #594 (comment))
So I think to handle recursive types automatically, you would ultimately need to have a second argument for
tangent_type
(actually I suppose it should be a callee of tangent_type so it doesn't have the constraints of a generated function) in the form of a type cache. Something like:function _resolve_tangent_type(::Type{P}, cache=IdDict{Any,Any}) where {P}This cache would need to be passed to downstream calls. It's kind of similar to your existing cache-like mechanisms, only here it would be for types rather than values.
Then what you would do is have a special sentinel struct for catching recursions:
struct UnderAssembly end const UNDER_ASSEMBLY = UnderAssembly()so that in your
tangent_type
call, you could have something like:cached_type = get(cache, P, nothing) if cached_type === nothing cache[P] = UNDER_ASSEMBLY result = #= Regular code =# cache[P] = result # Indicate it is finished return result elseif cached_type === UNDER_ASSEMBLY ###################################### #= This means we have a recursion!! =# ###################################### result = #= handle of recursive structs =# cache[P] = result # Regular caching return result else return cached_type # Already computed endNow, you want this to be a local cache rather than global, to prevent any world age problems. Which is why I think having a second argument to
tangent_type
makes the most sense.This can follow on #600 which means you would do all of this in the codegen step, so you wouldn't get any allocations at runtime.