Skip to content

Commit a2be715

Browse files
authored
gf: allow method shadowing with exact signatures without deletion (#53415)
We already allowed method shadowing for an inexact signature and implicit method deletion by exact signatures, so it was fairly arbitrary to implement it as a deletion for a replacement by an exact signature rather than use the morespecific definition for this. This change should be useful to Mocking libraries, such as Pluto, which previously had a buggier version of this which tried to re-activate or re-define the old methods. There is a bit of code cleanup in here, either of unused features, or of aspects that were broken, or of world_valid computations that were not actually impacting the final result (e.g. when there is a more specific result matches, it does not need to limit the result valid world range because of a less specific result that does not get returned).
1 parent 7f92880 commit a2be715

File tree

16 files changed

+161
-109
lines changed

16 files changed

+161
-109
lines changed

NEWS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ New language features
77
Language changes
88
----------------
99

10+
- When methods are replaced with exactly equivalent ones, the old method is no
11+
longer deleted implicitly simultaneously, although the new method does take
12+
priority and become more specific than the old method. Thus if the new
13+
method is deleted later, the old method will resume operating. This can be
14+
useful to mocking frameworks (such as in SparseArrays, Pluto, and Mocking,
15+
among others), as they do not need to explicitly restore the old method.
16+
While inference and compilation still must be repeated with this, it also
17+
may pave the way for inference to be able to intelligently re-use the old
18+
results, once the new method is deleted. ([#53415])
19+
1020
Compiler/Runtime improvements
1121
-----------------------------
1222

base/deprecated.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ To prevent `old` from being exported, set `export_old` to `false`.
2929
3030
# Examples
3131
```jldoctest
32-
julia> @deprecate old(x) new(x)
33-
old (generic function with 1 method)
32+
julia> @deprecate old_export(x) new(x)
33+
old_export (generic function with 1 method)
3434
35-
julia> @deprecate old(x) new(x) false
36-
old (generic function with 1 method)
35+
julia> @deprecate old_public(x) new(x) false
36+
old_public (generic function with 1 method)
3737
```
3838
3939
Calls to `@deprecate` without explicit type-annotations will define

base/reflection.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,7 @@ A special case where exact behavior is guaranteed: when `T <: S`,
887887
typeintersect(@nospecialize(a), @nospecialize(b)) = (@_total_meta; ccall(:jl_type_intersection, Any, (Any, Any), a::Type, b::Type))
888888

889889
morespecific(@nospecialize(a), @nospecialize(b)) = (@_total_meta; ccall(:jl_type_morespecific, Cint, (Any, Any), a::Type, b::Type) != 0)
890+
morespecific(a::Method, b::Method) = ccall(:jl_method_morespecific, Cint, (Any, Any), a, b) != 0
890891

891892
"""
892893
fieldoffset(type, i)
@@ -2492,7 +2493,7 @@ function isambiguous(m1::Method, m2::Method; ambiguous_bottom::Bool=false)
24922493
for match in ms
24932494
m = match.method
24942495
match.fully_covers || continue
2495-
if minmax === nothing || morespecific(m.sig, minmax.sig)
2496+
if minmax === nothing || morespecific(m, minmax)
24962497
minmax = m
24972498
end
24982499
end
@@ -2502,8 +2503,8 @@ function isambiguous(m1::Method, m2::Method; ambiguous_bottom::Bool=false)
25022503
for match in ms
25032504
m = match.method
25042505
m === minmax && continue
2505-
if !morespecific(minmax.sig, m.sig)
2506-
if match.fully_covers || !morespecific(m.sig, minmax.sig)
2506+
if !morespecific(minmax, m)
2507+
if match.fully_covers || !morespecific(m, minmax)
25072508
return true
25082509
end
25092510
end

doc/src/devdocs/types.md

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -521,10 +521,6 @@ than the other.) Likewise, `Tuple{Int,Vararg{Int}}` is not a subtype of `Tuple{
521521
considered more specific. However, `morespecific` does get a bonus for length: in particular,
522522
`Tuple{Int,Int}` is more specific than `Tuple{Int,Vararg{Int}}`.
523523

524-
If you're debugging how methods get sorted, it can be convenient to define the function:
525-
526-
```julia
527-
type_morespecific(a, b) = ccall(:jl_type_morespecific, Cint, (Any,Any), a, b)
528-
```
529-
530-
which allows you to test whether tuple type `a` is more specific than tuple type `b`.
524+
Additionally, if 2 methods are defined with identical signatures, per type-equal, then they
525+
will instead by compared by order of addition, such that the later method is more specific
526+
than the earlier one.

doc/src/manual/modules.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,6 @@ Stacktrace:
184184
@ none:0
185185
[2] top-level scope
186186
@ none:1
187-
188187
```
189188

190189
This error prevents accidentally adding methods to functions in other modules that you only intended to use.
@@ -196,17 +195,16 @@ julia> using .NiceStuff
196195
julia> struct Cat end
197196
198197
julia> NiceStuff.nice(::Cat) = "nice 😸"
199-
200198
```
201199

202200
Alternatively, you can `import` the specific function name:
203201
```jldoctest module_manual
204202
julia> import .NiceStuff: nice
205203
206-
julia> struct Cat end
204+
julia> struct Mouse end
207205
208-
julia> nice(::Cat) = "nice 😸"
209-
nice (generic function with 2 methods)
206+
julia> nice(::Mouse) = "nice 🐭"
207+
nice (generic function with 3 methods)
210208
```
211209

212210
Which one you choose is a matter of style. The first form makes it clear that you are adding a

src/gf.c

Lines changed: 80 additions & 57 deletions
Large diffs are not rendered by default.

src/jl_exported_funcs.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@
479479
XX(jl_type_intersection) \
480480
XX(jl_type_intersection_with_env) \
481481
XX(jl_type_morespecific) \
482-
XX(jl_type_morespecific_no_subtype) \
482+
XX(jl_method_morespecific) \
483483
XX(jl_type_union) \
484484
XX(jl_type_unionall) \
485485
XX(jl_unbox_bool) \

src/julia.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1711,6 +1711,7 @@ JL_DLLEXPORT jl_value_t *jl_type_unionall(jl_tvar_t *v, jl_value_t *body);
17111711
JL_DLLEXPORT const char *jl_typename_str(jl_value_t *v) JL_NOTSAFEPOINT;
17121712
JL_DLLEXPORT const char *jl_typeof_str(jl_value_t *v) JL_NOTSAFEPOINT;
17131713
JL_DLLEXPORT int jl_type_morespecific(jl_value_t *a, jl_value_t *b);
1714+
JL_DLLEXPORT int jl_method_morespecific(jl_method_t *ma, jl_method_t *mb);
17141715

17151716
STATIC_INLINE int jl_is_dispatch_tupletype(jl_value_t *v) JL_NOTSAFEPOINT
17161717
{

src/julia_internal.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1465,8 +1465,6 @@ struct jl_typemap_assoc {
14651465
size_t const world;
14661466
// outputs
14671467
jl_svec_t *env; // subtype env (initialize to null to perform intersection without an environment)
1468-
size_t min_valid;
1469-
size_t max_valid;
14701468
};
14711469

14721470
jl_typemap_entry_t *jl_typemap_assoc_by_type(

src/subtype.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5150,6 +5150,26 @@ JL_DLLEXPORT int jl_type_morespecific_no_subtype(jl_value_t *a, jl_value_t *b)
51505150
return type_morespecific_(a, b, a, b, 0, NULL);
51515151
}
51525152

5153+
// Equivalent to `jl_type_morespecific` of the signatures, except that more recent
5154+
// methods are more specific, iff the methods signatures are type-equal
5155+
JL_DLLEXPORT int jl_method_morespecific(jl_method_t *ma, jl_method_t *mb)
5156+
{
5157+
jl_value_t *a = (jl_value_t*)ma->sig;
5158+
jl_value_t *b = (jl_value_t*)mb->sig;
5159+
if (obviously_disjoint(a, b, 1))
5160+
return 0;
5161+
if (jl_has_free_typevars(a) || jl_has_free_typevars(b))
5162+
return 0;
5163+
if (jl_subtype(b, a)) {
5164+
if (jl_types_equal(a, b))
5165+
return jl_atomic_load_relaxed(&ma->primary_world) > jl_atomic_load_relaxed(&mb->primary_world);
5166+
return 0;
5167+
}
5168+
if (jl_subtype(a, b))
5169+
return 1;
5170+
return type_morespecific_(a, b, a, b, 0, NULL);
5171+
}
5172+
51535173
#ifdef __cplusplus
51545174
}
51555175
#endif

0 commit comments

Comments
 (0)