Skip to content

Commit ff5c26f

Browse files
committed
test that various interface callables behave well
I call callables that are meant to have methods added to by package authors "interface callables". Test that various interface callables are well-behaved in case of the introduction of a new applicable type. Only interface callables taking a single type argument are tested. The specific properties tested: * `max_methods` is high enough for good inference * the number of matching methods for a new type is not excessive The primary motivation for this change is to make any future lowering of the `max_methods` setting of the interface callables safer. To make the tests pass, fix a recent regression in base/strings/search.jl. This change depends on PR #58788 for `eltype`.
1 parent 5416edb commit ff5c26f

File tree

3 files changed

+79
-3
lines changed

3 files changed

+79
-3
lines changed

base/strings/search.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,12 @@ struct RvCharPosIter{S}
102102
last_char_byte::UInt8
103103
end
104104

105-
IteratorSize(s::Type{<:Union{FwCharPosIter, RvCharPosIter}}) = SizeUnknown()
106-
eltype(::Type{<:Union{FwCharPosIter, RvCharPosIter}}) = Int
105+
@nospecializeinfer function IteratorSize(@nospecialize unused::Union{Type{<:FwCharPosIter}, Type{<:RvCharPosIter}})
106+
SizeUnknown()
107+
end
108+
@nospecializeinfer function eltype(@nospecialize unused::Union{Type{<:FwCharPosIter}, Type{<:RvCharPosIter}})
109+
Int
110+
end
107111

108112
function RvCharPosIter(s::Union{String, SubString{String}}, c::AbstractChar)
109113
char = Char(c)::Char

test/choosetests.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const STDLIBS = filter!(x -> isfile(joinpath(STDLIB_DIR, x, "src", "$(x).jl")),
77

88
const TESTNAMES = [
99
"subarray", "core", "compiler", "compiler_extras", "worlds", "atomics",
10-
"keywordargs", "numbers", "subtype",
10+
"keywordargs", "numbers", "subtype", "interface_callables",
1111
"char", "strings", "triplequote", "unicode", "intrinsics",
1212
"dict", "hashing", "iobuffer", "staged", "offsetarray",
1313
"arrayops", "tuple", "reduce", "reducedim", "abstractarray",

test/interface_callables.jl

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using Test
2+
3+
module ExampleTypes end
4+
5+
@testset "interface callables" begin
6+
Base.@nospecializeinfer function max_methods_of(@nospecialize callable::Any)
7+
raw = Base._stable_typeof(callable).name.max_methods
8+
if iszero(raw)
9+
3 # default value, TODO: how to avoid hardcoding this?
10+
else
11+
Int(raw)
12+
end
13+
end
14+
"""
15+
interface_callables::Dict{DataType, Vector{Any}}
16+
17+
In each key-value pair:
18+
19+
* the key is the direct supertype of the newly-defined type
20+
21+
* the values are the applicable interface callables
22+
"""
23+
interface_callables = let
24+
nums = Any[widen, zero, one, oneunit]
25+
keys_and_values = Any[
26+
(Any => Any[eltype, Base.IteratorSize, Base.IteratorEltype]),
27+
(DenseVector{Float32} => Any[Base.elsize]),
28+
(Real => nums),
29+
(AbstractFloat => nums),
30+
(Integer => nums),
31+
]
32+
Dict{DataType, Vector{Any}}(keys_and_values)
33+
end
34+
interface_callables_supertypes = collect(DataType, keys(interface_callables))
35+
function example_type_name(i::Int, j::Int)
36+
n = string('_', i, '_', j)
37+
Symbol(n)
38+
end
39+
function tests(interface_callables, interface_callables_supertypes)
40+
for (i, supertype) enumerate(interface_callables_supertypes)
41+
for (j, callable) enumerate(interface_callables[supertype])
42+
typ = getproperty(ExampleTypes, example_type_name(i, j))
43+
method_match_length = length(methods(callable, Tuple{Type{<:typ}}))
44+
# `max_methods` is high-enough for good inference
45+
@test method_match_length max_methods_of(callable)
46+
# don't match an excessive number of methods for the newly-defined type
47+
@test method_match_length 2
48+
end
49+
end
50+
end
51+
for (i, supertype) enumerate(interface_callables_supertypes) # define types
52+
for (j, _) enumerate(interface_callables[supertype])
53+
type_name = example_type_name(i, j)
54+
@eval ExampleTypes struct $type_name{X} <: $supertype
55+
x::X
56+
end
57+
end
58+
end
59+
@testset "first pass: without any new method defined for the new type" begin
60+
tests(interface_callables, interface_callables_supertypes)
61+
end
62+
for (i, supertype) enumerate(interface_callables_supertypes) # define methods for new types
63+
for (j, callable) enumerate(interface_callables[supertype])
64+
callable_name = nameof(callable)
65+
type_name = example_type_name(i, j)
66+
@eval ExampleTypes function Base.$callable_name(::Type{<:$type_name}) end
67+
end
68+
end
69+
@testset "second pass: with a new method defined for the new type" begin
70+
tests(interface_callables, interface_callables_supertypes)
71+
end
72+
end

0 commit comments

Comments
 (0)