Recently Keno merged a few PRs (like this one) that introduce binding partitions. One consequence is that we will now be able to redefine structs. However, there are some other consequences. According to this PR, those consequences include
forbid assignment to constants
The need for invokelatest when accessing globals defined in a different world age.
I haven’t been following the development of binding partitions very closely, but at first glance those sound like they might be breaking changes.
Are we certain that there are no breaking changes introduced by binding partitions?
Here’s an example of code that runs on 1.11 but not on 1.12:
1.11
julia> VERSION
v"1.11.4"
julia> module M1; const x = 1; export x; end
Main.M1
julia> module M2; const x = 2; export x; end
Main.M2
julia> using .M1
julia> x
1
julia> using .M2
WARNING: using M2.x in module Main conflicts with an existing identifier.
julia> x
1
1.12
julia> VERSION
v"1.12.0-beta2"
julia> module M1; const x = 1; export x; end
Main.M1
julia> module M2; const x = 2; export x; end
Main.M2
julia> using .M1
julia> x
1
julia> using .M2
julia> x
ERROR: UndefVarError: `x` 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.
Is the reasoning here that the behavior on 1.11 is ambiguous or UB? Regardless, I wouldn’t be surprised if there is code out in the wild that breaks because of this change. Hopefully someone who knows more about this can chime in.
There are a number of minor behavioral changes like the one you pointed out, mostly in places that were previously warnings or where the compiler would arbitrarily pick one behavior or the other. The rules have been tightened to make all of these well defined. In general, we tried to pick the least invasive change possible, but 100% identical behavior was not possible. We looked at PkgEval as well of course to try to keep the impact minimal.
A particular point to make is that a lot of these things were completely ill-defined from the language perspective. Of course there were things that the implementation happened to do, but if you gave someone a julia program in the abstract, it was not generally possible to tell what binding a particular identifier would resolve to. It would heavily depend on internals. For example when and if the compiler ran. It could also change between versions. A lot of the conceptual work that went into 1.12 was looking at all of these cases and coming up with language level semantics that were close to what the implementation was already doing, but completely grounded in concepts available to the language semantics. Fortunately that also means that these things (after 1.12 of course) should no longer depend on what version of the compiler you happen to be running.