Skip to content

InteractiveUtils: access bindings in latest world#60736

Open
timholy wants to merge 2 commits intomasterfrom
teh/iutils_world
Open

InteractiveUtils: access bindings in latest world#60736
timholy wants to merge 2 commits intomasterfrom
teh/iutils_world

Conversation

@timholy
Copy link
Copy Markdown
Member

@timholy timholy commented Jan 19, 2026

InteractiveUtils provides a number of utilities for querying and accessing the contents of modules. Because these modules can gain new bindings, some callers of InteractiveUtils functions produce warnings

WARNING: Detected access to binding ... prior to its definition world.

One dramatic example occurs with the sequence

using OptimizationProblems: OptimizationProblems
using OptimizationProblems.ADNLPProblems
using ADNLPModels: ADNLPModel

when running with Revise, where it can produce hundreds of warnings. ADNLPProblems loads new problems via Requires, and starting with v3.13 Revise queries InteractiveUtils.subtypes to cache dependent fieldtypes. This triggers the warning, and because it runs in a separate task, the --depwarn=error trick unfortunately failed to produce a stacktrace. Thus this was quite difficult to track down.

Fixes timholy/Revise.jl#993

CC @aviatesk, @Keno

@timholy timholy added backport 1.12 Change should be backported to release-1.12 backport 1.13 Change should be backported to release-1.13 labels Jan 19, 2026
@timholy
Copy link
Copy Markdown
Member Author

timholy commented Jan 19, 2026

The main alternative to this approach might be to give names the option to cut off names at the current task's world age.

@DilumAluthge DilumAluthge mentioned this pull request Jan 19, 2026
40 tasks
@topolarity topolarity requested a review from Keno January 20, 2026 20:53
@Keno
Copy link
Copy Markdown
Member

Keno commented Jan 20, 2026

I think it'd prefer if all of these gained a world kwarg (ok to default to latest) so that the query happens consistently in one particular world (possibly captured on function entry).

@KristofferC KristofferC mentioned this pull request Jan 26, 2026
43 tasks
@KristofferC KristofferC mentioned this pull request Feb 4, 2026
56 tasks
@timholy
Copy link
Copy Markdown
Member Author

timholy commented Feb 25, 2026

@Keno updated. Perhaps the main thing to decide is whether get_world_counter or tls_world_age is the right default for InteractiveUtils. An argument to use get_world_counter (which is the current state of this PR) is that interactive tools might want to be comprehensive by default, whereas code callers (which have a lower penalty on verbosity) are more likely to want to narrow the range.

@Keno
Copy link
Copy Markdown
Member

Keno commented Feb 25, 2026

get_world_counter is the right default for InteractiveUtils.

InteractiveUtils provides a number of utilities for querying and
accessing the contents of modules. Because these modules can gain new
bindings, some callers of InteractiveUtils functions produce warnings

    WARNING: Detected access to binding ... prior to its definition world.

One dramatic example occurs with the sequence

    using OptimizationProblems: OptimizationProblems
    using OptimizationProblems.ADNLPProblems
    using ADNLPModels: ADNLPModel

when running with Revise. ADNLPProblems loads new problems via Requires,
and starting with v3.13 Revise queries `InteractiveUtils.subtypes` to
cache dependent fieldtypes. This triggers the warning.

To fix this, add `defined_since` and `world` keyword arguments to
`names`/`unsorted_names` to filter the returned names by world age.
These are then threaded through `varinfo`, `methodswith`, and `subtypes`
in InteractiveUtils so they operate on bindings in the correct world.

The defaults for `defined_since` and `world` kwargs are set to preserve
the current behavior. Revise will be able to eliminate the warnings by
passing `world=Base.tls_world_age()` to `subtypes`.

Related: timholy/Revise.jl#993

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
src/module.c Outdated
continue;
enum jl_partition_kind kind = jl_binding_kind(bpart);
if (((jl_atomic_load_relaxed(&b->flags) & BINDING_FLAG_PUBLICP) ||
if ((((jl_atomic_load_relaxed(&b->flags) & BINDING_FLAG_PUBLICP) && !jl_bkind_is_some_guard(kind)) ||
Copy link
Copy Markdown
Member Author

@timholy timholy Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check---world wasn't working as intended without it, pre-definition queries were still returning the name.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a behavioral change. E.g. for export'd bindings that were not assigned.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe? Undefined and public is still an option. This is not really about what's correct or not, more about what these options map to. My general preference would be not to change that since external packages are surprisingly sensitive to this list.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to filter something specific I'd rather add another boolean flag.

Copy link
Copy Markdown
Member Author

@timholy timholy Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fundamentally the issue is that the guard entries for newly-created bindings in a module get backdated to the module age:

julia> module M
       public z
       end
Main.M

julia> age = Int(Base.get_world_counter())
39879

julia> b = GlobalRef(M, :z).binding
Binding Main.M.z
   39874:- undefined binding - guard entry

julia> age2 = Int(Base.get_world_counter())
39881

julia> @eval M z=1
1

julia> b = GlobalRef(M, :z).binding
Binding Main.M.z
   39882:- global variable with type Any
   39874:39881 - undefined binding - guard entry

julia> @eval M y=1
1

julia> b = GlobalRef(M, :y).binding
Binding Main.M.y
   39883:- global variable with type Any
   39874:39882 - undefined binding - guard entry

Assuming this is necessary, I'm not sure how to identify the world age at time of creation.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

xref #61200

timholy added a commit that referenced this pull request Feb 28, 2026
This adds a new binding partition kind `PARTITION_KIND_DECLARED_GUARD` to
distinguish bindings that were explicitly declared via `public x` or `export x`
on an undefined name from purely implicit guard partitions created by lookup
misses.

Previously, `export foo` on an undefined name would set the EXPORTED flag on
the existing GUARD partition. With this change, it creates a new DECLARED_GUARD
partition, giving the explicit declaration its own world-stamped record. This
allows tooling and error messages to distinguish "someone explicitly declared
this name" from "this name was simply never resolved."

This will be useful in resolving the challenges currently facing #60736.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@KristofferC KristofferC mentioned this pull request Mar 13, 2026
39 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport 1.12 Change should be backported to release-1.12 backport 1.13 Change should be backported to release-1.13

Projects

None yet

Development

Successfully merging this pull request may close these issues.

World age warning when using with numbered prompt.

3 participants