Skip to content

QA: scan the Functors package extension - #331

Merged
ChrisRackauckas merged 2 commits into
SciML:masterfrom
ChrisRackauckas-Claude:qa-check-extensions
Aug 1, 2026
Merged

ChrisRackauckas merged 2 commits into
SciML:masterfrom
ChrisRackauckas-Claude:qa-check-extensions

Conversation

@ChrisRackauckas-Claude

@ChrisRackauckas-Claude ChrisRackauckas-Claude commented Aug 1, 2026 •

Copy link
Copy Markdown
Member

This PR should be ignored until reviewed by @ChrisRackauckas.

Problem

run_qa runs ExplicitImports' checks over the package module and its extensions — ExplicitImports reads the [extensions] table from Project.toml and adds each one — but only when the extension module actually exists:

ext_mod = Base.get_extension(mod, Symbol(ext))
ext_mod === nothing && continue

An extension module only exists once its trigger weakdep is loaded, and the QA environment loaded none. So DiffEqCallbacksFunctorsExt was never checked by QA.

Note the failure mode is silent: an unresolvable extension is skipped and the checks still report a clean pass.

Change

  • test/qa/Project.toml: add Functors (same UUID as the root [weakdeps] entry) with compat = "0.5", mirroring the root [compat].
  • test/qa/qa.jl: using Functors before run_qa, with a comment explaining why.
  • test/qa/qa.jl: a load guard asserting the extension module really exists, so a future break in the extension's precompilation fails QA instead of silently reverting coverage to zero:
# ExplicitImports silently skips an extension that fails to load, so assert the
# extension modules actually exist rather than trusting a green run_qa.
@testset "Extensions loaded" begin
    @test Base.get_extension(DiffEqCallbacks, :DiffEqCallbacksFunctorsExt) !== nothing
end

That guard matters more than usual here, since the 22 ignore entries below are all justified by the extension being scanned.

Proof the extension is actually scanned now

A passing QA summary is not proof — each ExplicitImports check folds all submodules into a single @test, so a clean extension yields an identical summary either way. Run directly against the QA environment:

$ julia --project=test/qa -e 'using DiffEqCallbacks, Functors, SciMLTesting
      const EI = SciMLTesting.ExplicitImports
      println("get_extension => ", Base.get_extension(DiffEqCallbacks, :DiffEqCallbacksFunctorsExt))
      println("modules scanned: ", first.(EI.explicit_imports(DiffEqCallbacks)))'
get_extension => DiffEqCallbacksFunctorsExt
modules scanned: Module[DiffEqCallbacksFunctorsExt, DiffEqCallbacks]

versus the same script without Functors (i.e. what master does today):

get_extension => nothing
modules scanned: Module[DiffEqCallbacks]

The first QA run after adding the weakdep also errored with 22 new all_explicit_imports_are_public findings from ext/DiffEqCallbacksFunctorsExt.jl, which is independent confirmation that the extension entered the checked set.

Coverage

Extension Status
DiffEqCallbacksFunctorsExt (trigger: Functors) now scanned, and guarded

That is the package's only extension, so coverage is complete — nothing is left out for GPU-hardware, external-system-library, or resolution reasons.

Ignore entries added

One block, under all_explicit_imports_are_public, covering 22 symbols:

allocate_vjp, allocate_vjp_internal, allocate_zeros, internal_add!, internal_adjoint, internal_allocate_zeros, internal_axpy!, internal_copy, internal_copyto!, internal_neg!, internal_scalar_mul!, internal_sub!, internal_zero!, recursive_add!, recursive_adjoint, recursive_axpy!, recursive_copy, recursive_copyto!, recursive_neg!, recursive_scalar_mul!, recursive_sub!, recursive_zero!

Justification: every one of these is a DiffEqCallbacks internal generic that the extension imports from its own parent package in order to add Functors-traversable methods to it — that is the entire purpose of the extension. There is no public spelling, and promoting 22 internal recursive-container helpers to public API (which under the SciML rules would also require docstrings and rendered docs entries, plus a SemVer commitment) would be the wrong fix. Qualifying instead of importing (DiffEqCallbacks.recursive_copyto!(y, x) = ...) just moves the same finding to all_qualified_accesses_are_public.

ExplicitImports' allow_internal_imports = true is meant to exempt exactly this kind of same-package import, but it tests Base.moduleroot(mod) == Base.moduleroot(importing_from), and an extension module is its own moduleroot rather than the parent package's — so parent-package imports from an extension are not recognised as internal. Arguably worth an upstream issue on ExplicitImports.jl; ignoring here in the meantime.

No check was disabled, no @test_broken / @test_skip was added.

Extension source changes

None. The findings were all the unavoidable parent-package-internals class above; there were no no_implicit_imports or *_via_owners findings to fix.

Local result

$ GROUP=QA julia --project=. -e 'using Pkg; Pkg.test()'
Test Summary:   | Pass  Total  Time
QA/jet_tests.jl |   15     15  7.5s
Test Summary: | Pass  Total     Time
QA/qa.jl      |   22     22  1m51.4s
     Testing DiffEqCallbacks tests passed

Julia 1.12. The QA/qa.jl count went 20 passed + 1 errored (weakdep added, before the ignore block) → 21 (ignores added) → 22 (load guard added), the last increment being the new Extensions loaded assertion.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Yb5kCpT5SRzTrhppKSh1n7

ExplicitImports discovers extension modules via `Base.get_extension`, which
returns `nothing` until the trigger weakdep is loaded, so the QA lane was
scanning only `DiffEqCallbacks` itself and never `DiffEqCallbacksFunctorsExt`.
Adding Functors to the QA environment and loading it in `qa.jl` brings the
extension into the checked module set.

The newly-surfaced findings are all the extension importing the parent
package's internal recursive-container generics, which is what the extension
exists to implement; they are ignored with a comment rather than promoted to
public API.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
ExplicitImports silently skips an extension whose module cannot be resolved:
`Base.get_extension` returns `nothing` and every check reports a clean pass. A
later break in the extension's precompilation would therefore turn QA green
while extension coverage silently dropped back to zero, with the 22
`all_explicit_imports_are_public` ignores still riding on a module nothing was
checking.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
@ChrisRackauckas
ChrisRackauckas marked this pull request as ready for review August 1, 2026 08:40
@ChrisRackauckas
ChrisRackauckas merged commit 1fac1f5 into SciML:master Aug 1, 2026
17 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants