From bacef4fe123fd1de5e9c10a79d5052d8d1946a99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller-Widmann?= Date: Thu, 15 Oct 2026 11:21:10 +0200 Subject: [PATCH 1/3] Use `Type{T}` self-types for rules on types on Julia 1.14 On Julia 1.14, `Core.Typeof(T)` returns `Core.TypeEgal{T}` for a type `T`, making macro-generated rules ambiguous with hand-written `::Type{T}` rules. Fixes #713 Co-Authored-By: Claude Opus 5.5 --- Project.toml | 2 +- src/rule_definition_tools.jl | 11 +++++++---- test/rule_definition_tools.jl | 13 +++++++++++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Project.toml b/Project.toml index 85e4ca031..5d434b03b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ChainRulesCore" uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -version = "1.26.1" +version = "1.26.2" [deps] Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" diff --git a/src/rule_definition_tools.jl b/src/rule_definition_tools.jl index 16205228b..c81eb3e62 100644 --- a/src/rule_definition_tools.jl +++ b/src/rule_definition_tools.jl @@ -5,6 +5,9 @@ macro strip_linenos(expr) return esc(Base.remove_linenums!(expr)) end +# `Core.Typeof` before Julia 1.14 (now `Core.TypeEqOf`), see JuliaLang/julia#62001 +_selftype(@nospecialize(x)) = isa(x, Type) ? Type{x} : typeof(x) + ############################################################################################ ### @scalar_rule @@ -183,7 +186,7 @@ function derivatives_given_output end function scalar_derivative_expr(__source__, f, setup_stmts, inputs, partials) return @strip_linenos quote function ChainRulesCore.derivatives_given_output( - $(esc(:Ω)), ::Core.Typeof($f), $(inputs...) + $(esc(:Ω)), ::$_selftype($f), $(inputs...) ) $(__source__) $(setup_stmts...) @@ -215,7 +218,7 @@ function scalar_frule_expr(__source__, f, call, setup_stmts, inputs, partials) return @strip_linenos quote # _ is the input derivative w.r.t. function internals. since we do not # allow closures/functors with @scalar_rule, it is always ignored - function ChainRulesCore.frule((_, $(Δs...)), ::Core.Typeof($f), $(inputs...)) + function ChainRulesCore.frule((_, $(Δs...)), ::$_selftype($f), $(inputs...)) $(__source__) $(esc(:Ω)) = $call $(setup_stmts...) @@ -255,7 +258,7 @@ function scalar_rrule_expr(__source__, f, call, setup_stmts, inputs, partials) end return @strip_linenos quote - function ChainRulesCore.rrule(::Core.Typeof($f), $(inputs...)) + function ChainRulesCore.rrule(::$_selftype($f), $(inputs...)) $(__source__) $(esc(:Ω)) = $call $(setup_stmts...) @@ -607,7 +610,7 @@ function _split_primal_name(primal_name) is_qualified = Meta.isexpr(primal_name, :(.)) is_parameterized = Meta.isexpr(primal_name, :curly) if is_plain || is_qualified || is_parameterized - primal_name_sig = :(::$Core.Typeof($primal_name)) + primal_name_sig = :(::$_selftype($primal_name)) return primal_name_sig, primal_name elseif Meta.isexpr(primal_name, :(::)) # e.g. (::T)(x, y) _primal_name = gensym(Symbol(:instance_, primal_name.args[end])) diff --git a/test/rule_definition_tools.jl b/test/rule_definition_tools.jl index 445536512..bad5b5b3d 100644 --- a/test/rule_definition_tools.jl +++ b/test/rule_definition_tools.jl @@ -29,6 +29,10 @@ struct NonDiffCounterExample x end +struct NonDiffTypeExample + x +end + module NonDiffModuleExample nondiff_2_1(x, y) = fill(7.5, 100)[x + y] end @@ -142,6 +146,15 @@ end # make sure that isn't happenning @test frule((ZeroTangent(), 1.2), NonDiffCounterExample, 2.0) === nothing @test rrule(NonDiffCounterExample, 2.0) === nothing + + # https://github.com/JuliaDiff/ChainRulesCore.jl/issues/713 + # a more specific hand-written `::Type{T}` rule must not be ambiguous + @non_differentiable NonDiffTypeExample(::Any) + function ChainRulesCore.rrule(::Type{NonDiffTypeExample}, x::Float64) + return NonDiffTypeExample(x), Δ -> (NoTangent(), 2Δ) + end + @test rrule(NonDiffTypeExample, 2.0)[2](1.2) == (NoTangent(), 2.4) + @test rrule(NonDiffTypeExample, 2)[2](1.2) == (NoTangent(), NoTangent()) end @testset "Varargs" begin From 0ee77b42cb6631b890a1447436620caea8f53a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller-Widmann?= Date: Thu, 15 Oct 2026 11:21:10 +0200 Subject: [PATCH 2/3] Remove `Core.Typeof` from docs Co-Authored-By: Claude Opus 5.5 --- docs/src/rule_author/rule_definition_tools.md | 4 +-- docs/src/rule_author/writing_good_rules.md | 27 ------------------- 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/docs/src/rule_author/rule_definition_tools.md b/docs/src/rule_author/rule_definition_tools.md index 50466941f..721af86ad 100644 --- a/docs/src/rule_author/rule_definition_tools.md +++ b/docs/src/rule_author/rule_definition_tools.md @@ -11,10 +11,10 @@ For example, instead of manually defining the `frule` and the `rrule` for string ``` defines the following `frule` and `rrule` automatically ```julia -function ChainRulesCore.frule(var"##_#1600", ::Core.Typeof(*), String::Any...; kwargs...) +function ChainRulesCore.frule(var"##_#1600", ::typeof(*), String::Any...; kwargs...) return (*(String...; kwargs...), NoTangent()) end -function ChainRulesCore.rrule(::Core.Typeof(*), String::Any...; kwargs...) +function ChainRulesCore.rrule(::typeof(*), String::Any...; kwargs...) return (*(String...; kwargs...), function var"*_pullback"(_) (ZeroTangent(), ntuple((_->NoTangent()), 0 + length(String))...) end) diff --git a/docs/src/rule_author/writing_good_rules.md b/docs/src/rule_author/writing_good_rules.md index 225bb7042..5c1bf9340 100644 --- a/docs/src/rule_author/writing_good_rules.md +++ b/docs/src/rule_author/writing_good_rules.md @@ -108,33 +108,6 @@ Use `Type{<:Bar}` (with the `<:`) for non-concrete types, such that the `rrule` In particular, be careful not to use `typeof(Bar)` here. Because `typeof(Bar)` is `DataType`, using this to define an `rrule`/`frule` will define an `rrule`/`frule` for all constructors. -You can check which to use with `Core.Typeof`: - -```julia-repl -julia> function foo end -foo (generic function with 0 methods) - -julia> typeof(foo) -typeof(foo) - -julia> Core.Typeof(foob) -typeof(foo) - -julia> typeof(Bar) -DataType - -julia> Core.Typeof(Bar) -Type{Bar} - -julia> abstract type AbstractT end - -julia> typeof(AbstractT) -DataType - -julia> Core.Typeof(AbstractT) -Type{AbstractT} -``` - ### Functors (callable objects) In contrast to defining a rule for a constructor, it is possible to define rules for calling an instance of an object. From 1f7cf23de7ad587452710faa90da8639590dca69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller-Widmann?= Date: Thu, 15 Oct 2026 16:09:17 +0200 Subject: [PATCH 3/3] Fix CI on Julia 1.13 and in the Downgrade job - `ProjectTo(::Symmetric)` returns a union of pointer-ful types, which is only returned unboxed since JuliaLang/julia#55045 (1.14.0-DEV.1348). Use `broken = VERSION < v"1.14.0-DEV.1348"` instead of the incorrect `VERSION > v"1.13"` cutoff. - Raise the StaticArrays test compat to 1.9.8: older versions import `require_one_based_indexing` from StaticArraysCore, which was removed in StaticArraysCore 1.4.4. Co-Authored-By: Claude Opus 5.5 --- Project.toml | 2 +- test/projection.jl | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Project.toml b/Project.toml index 5d434b03b..5ef139503 100644 --- a/Project.toml +++ b/Project.toml @@ -12,7 +12,7 @@ BenchmarkTools = "1.5" Compat = "4.15" FiniteDifferences = "0.12.32" OffsetArrays = "1.14" -StaticArrays = "1.9.2" +StaticArrays = "1.9.8" julia = "1.10" [extensions] diff --git a/test/projection.jl b/test/projection.jl index 68eefac3c..3d1e150bf 100644 --- a/test/projection.jl +++ b/test/projection.jl @@ -457,12 +457,11 @@ struct NoSuperType end @test 0 == @ballocated $pdiag(dx) setup = (dx = Diagonal(rand(10^3))) psymm = ProjectTo(Symmetric(rand(10^3, 10^3))) - allocs = @ballocated $psymm(dx) setup = (dx = Symmetric(rand(10^3, 10^3))) # 64 - if VERSION > v"1.13" - @test 0 == allocs - else - @test_broken 0 == allocs - end + # The Union{Symmetric{…,Matrix}, Symmetric{…,ReshapedArray}} return value is only + # unboxed since JuliaLang/julia#55045 (sret_union ABI for pointer-ful types) + @test 0 == @ballocated($psymm(dx), setup = (dx = Symmetric(rand(10^3, 10^3)))) broken = ( + VERSION < v"1.14.0-DEV.1348" + ) end @testset "#685" begin