julia
ffa3bc8d - module: warn consistently on deprecated bindings imported via `using` (#62450)

Commit
28 days ago
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
Author
Parents
Loading