module: warn consistently on deprecated bindings imported via `using` (#62450)
A binding deprecated in its defining module warns on access when reached
directly or through an implicit `using`, except for constants: the
implicit resolution copied a deprecated constant's value into a
`PARTITION_KIND_IMPLICIT_CONST` partition without copying deprecations.
Since there is no leaf binding left for the depwarn walk to reach, the
deprecation was silently dropped and access through `using` skipped the
warning.
Record the deprecation as part of the implicit resolution instead.
`jl_resolve_implicit_import` now carries the imported partition's
`PARTITION_FLAG_DEPRECATED`/`PARTITION_FLAG_DEPWARN` in its result and
stamps them onto the importer's own resolved partition, so the flag is
present where the access check looks, for constants and globals alike,
and transparently through reexports (a `using` of a module that merely
reexports a deprecated binding now reports it as deprecated without
having to walk to the defining owner). Because the flags come fresh from
each resolution rather than being inherited from the previous partition,
(un)deprecating the source forces a re-resolution, and
`jl_maybe_reresolve_implicit` compares them when deciding whether a
resolution actually changed. The value is still stored in the partition,
so it continues to be constant-folded and propagated by inference.
Two constants of equal value imported from different modules are now
unified by `jl_egal` rather than pointer identity, and their deprecation
flags are OR-ed together. Deprecation remains weak: a non-deprecated
import still wins over a deprecated one, and unequal values are still
ambiguous.
With the flag recorded on the importer's partition, `should_depwarn`
reduces to a check of the top partition, which is what codegen's
constant-folding path (`maybe_depwarn`) already does, so the dynamic and
compiled reads agree. `Base.isdeprecated` becomes accurate for
implicitly imported bindings — it resolves the binding rather than
reporting `false` when the importing module has no binding table entry
yet — and the walk that reports deprecation is generalized over which
flag it is looking for so `isdeprecated` and the access warning share
it. Its stale "vaguely broken" comment is replaced with a description of
what it does and does not cover (`@deprecate` deprecates a method, not
the binding). `jl_get_binding_value_seqcst` also routes through the
depwarn path, so a runtime `getglobal` warns like every other read.
The deprecation message needed a corresponding fix: a binding deprecated
only through `using` has no `_dep_message_<name>` in the accessing
module, since the message lives in the source module(s), which do not
export it. When the local lookup finds nothing, walk the module's
`using` list to each exported source of the name, resolve reexports to
the defining leaf, and print each deprecated source's message, so
importing the same deprecated constant from several modules reports
every one's guidance instead of falling back to describing the value.
The import-time warning also appended the message (`, use X instead.`)
after a completed sentence and a newline, leaving it stranded on its own
line; drop that trailing punctuation so it reads as one sentence, as the
access warning already does.
Finally, `Base.@deprecate_binding` — the macro users are meant to reach
for when deprecating a constant or global, and the one this fix makes
behave consistently — had no docstring and was not exported. Document it
and export it alongside `@deprecate`.
Assisted-by: Claude Opus 4.8