@@ -354,11 +354,76 @@ end
354354`true` if a call to method with signature `sig` is permitted to contain
355355`Libtask.produce` statements.
356356
357- This is an opt-in mechanism. the fallback method of this function returns `false` indicating
357+ This is an opt-in mechanism. The fallback method of this function returns `false` indicating
358358that, by default, we assume that calls do not contain `Libtask.produce` statements.
359359"""
360360might_produce (:: Type{<:Tuple} ) = false
361361
362+ """
363+ @might_produce(f)
364+
365+ If `f` is a function that may call `Libtask.produce` inside it, then `@might_produce(f)`
366+ will generate the appropriate methods needed to ensure that `Libtask.might_produce` returns
367+ `true` for **all** relevant signatures of `f`. This works even if `f` has methods with
368+ keyword arguments.
369+
370+ !!! note
371+ Because `@might_produce f` is applied to all possible signatures, there are performance
372+ penalties associated with marking all methods of `f` as produceable if only one method
373+ can actually call `produce`. If performance is critical, please use the
374+ [`might_produce`](@ref) function directly.
375+
376+ ```jldoctest might_produce_macro
377+ julia> # For this demonstration we need to mark `g` as not being inlineable.
378+ @noinline function g(x; y, z=0)
379+ produce(x + y + z)
380+ end
381+ g (generic function with 1 method)
382+
383+ julia> function f()
384+ g(1; y=2, z=3)
385+ end
386+ f (generic function with 1 method)
387+
388+ julia> # This returns nothing because `g` isn't yet marked as being able to `produce`.
389+ consume(Libtask.TapedTask(nothing, f))
390+
391+ julia> Libtask.@might_produce(g)
392+
393+ julia> # Now it works!
394+ consume(Libtask.TapedTask(nothing, f))
395+ 6
396+ """
397+ macro might_produce (f)
398+ # See https://github.com/TuringLang/Libtask.jl/issues/197 for discussion of this macro.
399+ quote
400+ function $ (Libtask). might_produce (:: Type{<:Tuple{typeof($(esc(f))),Vararg}} )
401+ return true
402+ end
403+ possible_n_kwargs = unique (map (length ∘ Base. kwarg_decl, methods ($ (esc (f)))))
404+ if possible_n_kwargs != [0 ]
405+ # Oddly we need to interpolate the module and not the function: either
406+ # `$(might_produce)` or $(Libtask.might_produce) seem more natural but both of
407+ # those cause the entire `Libtask.might_produce` to be treated as a single
408+ # symbol. See https://discourse.julialang.org/t/128613
409+ function $ (Libtask). might_produce (
410+ :: Type{<:Tuple{typeof(Core.kwcall),<:NamedTuple,typeof($(esc(f))),Vararg}}
411+ )
412+ return true
413+ end
414+ for n in possible_n_kwargs
415+ # We only need `Any` and not `<:Any` because tuples are covariant.
416+ kwarg_types = fill (Any, n)
417+ function $ (Libtask). might_produce (
418+ :: Type{<:Tuple{<:Function,kwarg_types...,typeof($(esc(f))),Vararg}}
419+ )
420+ return true
421+ end
422+ end
423+ end
424+ end
425+ end
426+
362427# Helper struct used in `derive_copyable_task_ir`.
363428struct TupleRef
364429 n:: Int
0 commit comments