WARNING: both X and Y export "F"; uses of it in module Z must be qualified

Some versions of Julia reported warnings like these ones:

WARNING: both IOExtras and Base export “readuntil”; uses of it in module WebSockets must be qualified
WARNING: both IOExtras and Base export “readuntil”; uses of it in module Strings must be qualified

These warnings seem to have gone now. What were the considerations to remove this? Was there a (community) discussion? I can’t access the git history over the company network for a closer investigation.

1 Like

Are you sure it was a change in Julia? Probably it’s just that those mentioned packages fixed their imports.

I downloaded the source code of Julia from GitHub - JuliaLang/julia: The Julia Programming Language as a zip file and grepped. I think the code to display the warning is gone.

This does change in the v1.12-beta. It’s part of the larger overhaul to how bindings are resolved and replaced. Documentation is in the process of being written.

You do still get a hint here:

julia> module X
       f = 1
       export f
       end
Main.X

julia> module Y
       f = 2
       export f
       end
Main.Y

julia> using .X, .Y

julia> f
ERROR: UndefVarError: `f` not defined in `Main`
Hint: It looks like two or more modules export different bindings with this name, resulting in ambiguity. Try explicitly importing it from a particular module, or qualifying the name with the module it should come from.
2 Likes

I am aware of the way to generate the runtime error. I was just wondering why the specific warning was removed. Apparently this is under reconstruction, which is perfectly fine. Can I safely assume that a similar warning will be generated by Julia in 1.12 while precompiling?

I don’t think so, not unless you actually touch the ambiguous binding. But I’m not certain on this point yet myself.

1 Like

I can confirm that I get these warnings when precompiling using Julia 1.10, and not when using Julia 1.11. Now I wonder: Should I try to fix these warnings or not?

Uhm… as I understand it, the warning indicates up front that the client package will error if Julia actually hits the lines if warned about earlier. Qualification of the intended serving package solves the ambiguity and it is as simple as adding the package name to the call. So f() becomes either X.f() or Y.f() if there are two packages involved. Probably you are the one using your code the most, so you are most affected if your nightly calculations fail over such triviality, so fixing it in your source code is a good idea IMHO. I am truly risk-averse, so I qualify anything that can be qualified. As a result, if (future) changes to client packages cause collisions like this, my code still runs because it is already qualified. So I am a fan of import rather than using, because import forces qualification.

Yeah, if there are two packages that are frequently used together, it might be something you want to pro-actively flag. Otherwise, it’s just fine unless you actually trip over it.

To me, this seems like a very similar case as method ambiguity detection. A long long time ago, we warned at any prospect of method ambiguities at definition time. That was very noisy and not needed in practice, so instead it moved into a test — you ensure X and Y don’t have ambiguities when used together with Test.detect_ambiguities(X, Y).

I think this warning is similarly better served as an explicit test. I’m not sure how to best construct such a test, though.

Well… such a test needs to be executed repeatedly and maintenance needs to be done to the depending package - just to be able to omit the package name. Also, developers benefit from the qualification, I think, especially if many packages are used. The package name improves code readability - but that is subjective.

The danger I see is as follows: Package P depends on packages A and B. Package P has using A and using B. Package A exports function f and P uses it without qualification because A exports it. Package P is finished and in production at some point. Now a developer of B implements a function called f in B with the same signature. As a result, P is broken due to the changes of B. There is no CI/CD pipeline that warns for it, because there is no delta in P. Now, one could argue that the Project.toml of P should restrict the upgrades of A and B… but that brings a dependency hell a step closer. Therefore, I like qualification. I don’t need to check that P needs maintenance - I have done it already pro-actively like you indicate. The “tripping over it” is a risk for my company and its customers, I think. Now my package needs maintenance when A.f is changed without backwards compatibility, but I think that risk is smaller and unavoidable.

I think the solution there is for Package P to run ExplicitImports.jl. Then it doesn’t matter if A or B introduce conflicting bindings; all bindings are explicitly resolved in P.

3 Likes

ExplictImports requires Julia 1.12, so I installed julia-1.12.0-beta2 for Linux. After some fiddling around, I got similar warnings on my terminal:

Precompiling packages finished.
  432 dependencies successfully precompiled in 449 seconds. 55 already precompiled.
  4 dependencies had output during precompilation:
 StableRNGs
  WARNING: Constructor for type "Sampler" was extended in `StableRNGs` without explicit qualification or import.
    NOTE: Assumed "Sampler" refers to `Random.Sampler`. This behavior is deprecated and may differ in future versions.`
    NOTE: This behavior may have differed in Julia versions prior to 1.12.
    Hint: If you intended to create a new generic function of the same name, use `function Sampler end`.
    Hint: To silence the warning, qualify `Sampler` as `Random.Sampler` in the method signature or explicitly `import Random: Sampler`.
  
 WebSockets
  WARNING: Imported binding Logging.termlength was undeclared at import time during import to WebSockets.
  WARNING: Imported binding Logging.showvalue was undeclared at import time during import to WebSockets.
  
 UTCDateTimes
  WARNING: Constructor for type "DateTime" was extended in `UTCDateTimes` without explicit qualification or import.
    NOTE: Assumed "DateTime" refers to `<null>.DateTime`. This behavior is deprecated and may differ in future versions.`
    NOTE: This behavior may have differed in Julia versions prior to 1.12.
    Hint: If you intended to create a new generic function of the same name, use `function DateTime end`.
    Hint: To silence the warning, qualify `DateTime` as `<null>.DateTime` in the method signature or explicitly `import <null>: DateTime`.
  
 PlotlyBase  DistributionsExt
  WARNING: Constructor for type "Plot" was extended in `DistributionsExt` without explicit qualification or import.
    NOTE: Assumed "Plot" refers to `PlotlyBase.Plot`. This behavior is deprecated and may differ in future versions.`
    NOTE: This behavior may have differed in Julia versions prior to 1.12.
    Hint: If you intended to create a new generic function of the same name, use `function Plot end`.
    Hint: To silence the warning, qualify `Plot` as `PlotlyBase.Plot` in the method signature or explicitly `import PlotlyBase: Plot`.
  
  12 dependencies errored.
  For a report of the errors see `julia> err`. To retry use `pkg> precompile`
        Info We haven't cleaned this depot up for a bit, running Pkg.gc()...
      Active manifest files: 6 found
      Active artifact files: 119 found
      Active scratchspaces: 7 found
     Deleted 33 package installations (11.357 MiB)
     Deleted 2 artifact installations (34.462 MiB)

julia> 

So it looks like these will replace the previous ones. I tried to reproduce this output, but that failed. Precompiling again suppresses this output. Is there a way to generate it again without using ExplictImports?

Indeed, it looks like ExplicitImports is a cool package. It clearly invites to change code in a good direction. Thanks for pointing it out.

Those warnings all look unrelated to ExplicitImports. You can reproduce all of these directly at the REPL:

julia> module T
       using Random: Sampler
       Sampler() = ()
       end
WARNING: Constructor for type "Sampler" was extended in `T` without explicit qualification or import.
  NOTE: Assumed "Sampler" refers to `Random.Sampler`. This behavior is deprecated and may differ in future versions.
  NOTE: This behavior may have differed in Julia versions prior to 1.12.
  Hint: If you intended to create a new generic function of the same name, use `function Sampler end`.
  Hint: To silence the warning, qualify `Sampler` as `Random.Sampler` in the method signature or explicitly `import Random: Sampler`.

julia> module A
       export foo
       end
Main.A

julia> module B
       using ..A: foo
       end
WARNING: Imported binding A.foo was undeclared at import time during import to B.
Main.B

For packages, it’s probably true that they’ll only appear at precompile time. Removing the compiled files should get them to reappear.