Skip to content

Commit ee6aaaf

Browse files
committed
inference: inter-procedural conditional constraint back-propagation
This PR propagates `Conditional`s inter-procedurally when a `Conditional` at return site imposes a constraint on the call arguments. When inference exits local frame and the return type is annotated as `Conditional`, it will be converted into `InterConditional` object, which is implemented in `Core` and can be directly put into the global cache. Finally after going back to caller frame, `InterConditional` will be re-converted into `Conditional` in the context of the caller frame. So now some simple "is-wrapper" functions will propagate its constraint as expected, e.g.: ```julia isaint(a) = isa(a, Int) @test Base.return_types((Any,)) do a isaint(a) && return a # a::Int return 0 end == [Int] isaint2(::Any) = false isaint2(::Int) = true @test Base.return_types((Any,)) do a isaint2(a) && return a # a::Int return 0 end == [Int] ``` This PR also tweaks `isnothing` and `ismissing` so that there is no longer any inferrability penalties to use them instead of `x === nothing` or `x === missing` e.g.: ```julia @test Base.return_types((Union{Nothing,Int},)) do a isnothing(a) && return 0 return a # a::Int end == [Int] ``` (and now we don't need something like JuliaLang#38636) --- A compile time comparison: > on the current master (82d79ce) ``` Sysimage built. Summary: Total ─────── 55.295376 seconds Base: ─────── 23.359226 seconds 42.2444% Stdlibs: ──── 31.934773 seconds 57.7531% JULIA usr/lib/julia/sys-o.a Generating REPL precompile statements... 29/29 Executing precompile statements... 1283/1283 Precompilation complete. Summary: Total ─────── 91.129162 seconds Generation ── 68.800937 seconds 75.4983% Execution ─── 22.328225 seconds 24.5017% LINK usr/lib/julia/sys.dylib ``` > on this PR (37e279b) ``` Sysimage built. Summary: Total ─────── 51.694730 seconds Base: ─────── 21.943914 seconds 42.449% Stdlibs: ──── 29.748987 seconds 57.5474% JULIA usr/lib/julia/sys-o.a Generating REPL precompile statements... 29/29 Executing precompile statements... 1357/1357 Precompilation complete. Summary: Total ─────── 88.956226 seconds Generation ── 67.077710 seconds 75.4053% Execution ─── 21.878515 seconds 24.5947% LINK usr/lib/julia/sys.dylib ``` Here is a sample code that benefits from this PR: ```julia function summer(ary) r = 0 for a in ary if ispositive(a) r += a end end r end ispositive(a) = isa(a, Int) && a > 0 ary = Any[] for _ in 1:100_000 if rand(Bool) push!(ary, rand(-100:100)) elseif rand(Bool) push!(ary, rand('a':'z')) else push!(ary, nothing) end end using BenchmarkTools @Btime summer($(ary)) ``` > on the current master (82d79ce) ``` ❯ julia summer.jl 1.214 ms (24923 allocations: 389.42 KiB) ``` > on this PR (37e279b) ``` ❯ julia summer.jl 421.223 μs (0 allocations: 0 bytes) ``` --- Within the `Conditional`/`InterConditional` framework, only a single constraint can be back-propagated inter-procedurally. This PR implements a naive heuristic to "pick up" a constraint to be propagated when a return type is a boolean. The heuristic may fail to select an "interesting" constraint in some cases. For example, we may expect `::Expr` constraint to be imposed on the first argument of `Meta.isexpr`, but with the original definition of `Meta.isexpr`, the heuristic ends up picking up a constraint on the second argument (i.e. `ex.head === head`). ```julia isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head x::Any if isexpr(x, :call) x.args # ideally `x::Expr` end ``` I think We can get rid of this limitation by extending `Conditional` and `InterConditional` so that they can convey multiple constraints, but I'd like to leave this as a future work. --- - closes JuliaLang#38636 - closes JuliaLang#37342
1 parent e8f23d7 commit ee6aaaf

File tree

11 files changed

+269
-43
lines changed

11 files changed

+269
-43
lines changed

base/boot.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ eval(Core, :(CodeInstance(mi::MethodInstance, @nospecialize(rettype), @nospecial
424424
mi, rettype, inferred_const, inferred, const_flags, min_world, max_world)))
425425
eval(Core, :(Const(@nospecialize(v)) = $(Expr(:new, :Const, :v))))
426426
eval(Core, :(PartialStruct(@nospecialize(typ), fields::Array{Any, 1}) = $(Expr(:new, :PartialStruct, :typ, :fields))))
427+
eval(Core, :(InterConditional(slot::Int, @nospecialize(vtype), @nospecialize(elsetype)) = $(Expr(:new, :InterConditional, :slot, :vtype, :elsetype))))
427428
eval(Core, :(MethodMatch(@nospecialize(spec_types), sparams::SimpleVector, method::Method, fully_covers::Bool) =
428429
$(Expr(:new, :MethodMatch, :spec_types, :sparams, :method, :fully_covers))))
429430

base/compiler/abstractinterpretation.jl

Lines changed: 85 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ function is_improvable(@nospecialize(rtype))
2424
# already at Bottom
2525
return rtype !== Union{}
2626
end
27-
# Could be improved to `Const` or a more precise PartialStruct
28-
return isa(rtype, PartialStruct)
27+
# Could be improved to `Const` or a more precise wrapper
28+
return isa(rtype, PartialStruct) || isa(rtype, InterConditional)
2929
end
3030

3131
function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), argtypes::Vector{Any}, @nospecialize(atype), sv::InferenceState,
@@ -191,6 +191,8 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
191191
end
192192
end
193193
end
194+
195+
@assert !(rettype isa Conditional) "invalid lattice element returned from inter-procedural context"
194196
#print("=> ", rettype, "\n")
195197
return CallMeta(rettype, info)
196198
end
@@ -1035,9 +1037,29 @@ function abstract_call(interp::AbstractInterpreter, fargs::Union{Nothing,Vector{
10351037
add_remark!(interp, sv, "Could not identify method table for call")
10361038
return CallMeta(Any, false)
10371039
end
1038-
return abstract_call_gf_by_type(interp, nothing, argtypes, argtypes_to_type(argtypes), sv, max_methods)
1040+
callinfo = abstract_call_gf_by_type(interp, nothing, argtypes, argtypes_to_type(argtypes), sv, max_methods)
1041+
return callinfo_from_interprocedural(callinfo, fargs)
1042+
end
1043+
callinfo = abstract_call_known(interp, f, fargs, argtypes, sv, max_methods)
1044+
return callinfo_from_interprocedural(callinfo, fargs)
1045+
end
1046+
1047+
function callinfo_from_interprocedural(callinfo::CallMeta, ea::Union{Nothing,Vector{Any}})
1048+
rt = callinfo.rt
1049+
if isa(rt, InterConditional)
1050+
if ea !== nothing
1051+
# convert inter-procedural conditional constraint from callee into the constraint
1052+
# on slots of the current frame; `InterConditional` only comes from a "valid"
1053+
# `abstract_call` as such its slot should always be within the bound of this
1054+
# call arguments `ea`
1055+
e = ea[rt.slot]
1056+
if isa(e, Slot)
1057+
return CallMeta(Conditional(e, rt.vtype, rt.elsetype), callinfo.info)
1058+
end
1059+
end
1060+
return CallMeta(widenconditional(rt), callinfo.info)
10391061
end
1040-
return abstract_call_known(interp, f, fargs, argtypes, sv, max_methods)
1062+
return callinfo
10411063
end
10421064

10431065
function sp_type_rewrap(@nospecialize(T), linfo::MethodInstance, isreturn::Bool)
@@ -1279,6 +1301,9 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState)
12791301
W = frame.ip
12801302
s = frame.stmt_types
12811303
n = frame.nstmts
1304+
nargs = frame.nargs
1305+
def = frame.linfo.def
1306+
isva = isa(def, Method) && def.isva
12821307
while frame.pc´´ <= n
12831308
# make progress on the active ip set
12841309
local pc::Int = frame.pc´´ # current program-counter
@@ -1321,12 +1346,8 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState)
13211346
frame.handler_at[l] = frame.cur_hand
13221347
changes_else = changes
13231348
if isa(condt, Conditional)
1324-
if condt.elsetype !== Any && condt.elsetype !== changes[slot_id(condt.var)]
1325-
changes_else = StateUpdate(condt.var, VarState(condt.elsetype, false), changes_else)
1326-
end
1327-
if condt.vtype !== Any && condt.vtype !== changes[slot_id(condt.var)]
1328-
changes = StateUpdate(condt.var, VarState(condt.vtype, false), changes)
1329-
end
1349+
changes_else = conditional_changes(changes_else, condt.elsetype, condt.var)
1350+
changes = conditional_changes(changes, condt.vtype, condt.var)
13301351
end
13311352
newstate_else = stupdate!(s[l], changes_else)
13321353
if newstate_else !== false
@@ -1340,15 +1361,42 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState)
13401361
end
13411362
elseif isa(stmt, ReturnNode)
13421363
pc´ = n + 1
1343-
rt = widenconditional(abstract_eval_value(interp, stmt.val, s[pc], frame))
1344-
if !isa(rt, Const) && !isa(rt, Type) && !isa(rt, PartialStruct)
1345-
# only propagate information we know we can store
1346-
# and is valid inter-procedurally
1364+
bestguess = frame.bestguess
1365+
rt = abstract_eval_value(interp, stmt.val, s[pc], frame)
1366+
if isva
1367+
# give up inter-procedural constraint back-propagation from vararg methods
1368+
# because types of same slot may differ between callee and caller
1369+
rt = widenconditional(rt)
1370+
else
1371+
if isa(rt, Conditional) && !(1 slot_id(rt.var) nargs)
1372+
# discard this `Conditional` imposed on non-call arguments,
1373+
# since it's not interesting in inter-procedural context;
1374+
# we may give constraints on other call argument
1375+
rt = widenconditional(rt)
1376+
end
1377+
if !isa(rt, Conditional) && rt Bool
1378+
if isa(bestguess, InterConditional)
1379+
# if the bestguess so far is already `Conditional`, try to convert
1380+
# this `rt` into `Conditional` on the slot to avoid overapproximation
1381+
# due to conflict of different slots
1382+
rt = boolean_rt_to_conditional(rt, changes, bestguess.slot)
1383+
elseif nargs 1
1384+
# pick up the first "interesting" slot, convert `rt` to its `Conditional`
1385+
# TODO: this is very naive heuristic, ideally we want `Conditional`
1386+
# and `InterConditional` to convey constraints on multiple slots
1387+
rt = boolean_rt_to_conditional(rt, changes, nargs > 1 ? 2 : 1)
1388+
end
1389+
end
1390+
end
1391+
# only propagate information we know we can store and is valid inter-procedurally
1392+
if isa(rt, Conditional)
1393+
rt = InterConditional(slot_id(rt.var), rt.vtype, rt.elsetype)
1394+
elseif !isa(rt, Const) && !isa(rt, Type) && !isa(rt, PartialStruct)
13471395
rt = widenconst(rt)
13481396
end
1349-
if tchanged(rt, frame.bestguess)
1397+
if tchanged(rt, bestguess)
13501398
# new (wider) return type for frame
1351-
frame.bestguess = tmerge(frame.bestguess, rt)
1399+
frame.bestguess = tmerge(bestguess, rt)
13521400
for (caller, caller_pc) in frame.cycle_backedges
13531401
# notify backedges of updated type information
13541402
typeassert(caller.stmt_types[caller_pc], VarTable) # we must have visited this statement before
@@ -1452,6 +1500,27 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState)
14521500
nothing
14531501
end
14541502

1503+
function conditional_changes(changes::VarTable, @nospecialize(typ), var::Slot)
1504+
if typ (changes[slot_id(var)]::VarState).typ
1505+
return StateUpdate(var, VarState(typ, false), changes)
1506+
end
1507+
return changes
1508+
end
1509+
1510+
function boolean_rt_to_conditional(@nospecialize(rt), state::VarTable, slot_id::Int)
1511+
typ = widenconditional((state[slot_id]::VarState).typ) # avoid nested conditional
1512+
if isa(rt, Const)
1513+
if rt.val === true
1514+
return Conditional(SlotNumber(slot_id), typ, Bottom)
1515+
elseif rt.val === false
1516+
return Conditional(SlotNumber(slot_id), Bottom, typ)
1517+
end
1518+
elseif rt === Bool
1519+
return Conditional(SlotNumber(slot_id), typ, typ)
1520+
end
1521+
return rt
1522+
end
1523+
14551524
# make as much progress on `frame` as possible (by handling cycles)
14561525
function typeinf_nocycle(interp::AbstractInterpreter, frame::InferenceState)
14571526
typeinf_local(interp, frame)

base/compiler/typeinfer.jl

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -286,28 +286,32 @@ end
286286
function CodeInstance(result::InferenceResult, @nospecialize(inferred_result::Any),
287287
valid_worlds::WorldRange)
288288
local const_flags::Int32
289+
res = result.result
289290
if inferred_result isa Const
290291
# use constant calling convention
291292
rettype_const = (result.src::Const).val
292293
const_flags = 0x3
293294
inferred_result = nothing
294295
else
295-
if isa(result.result, Const)
296-
rettype_const = (result.result::Const).val
296+
if isa(res, Const)
297+
rettype_const = res.val
297298
const_flags = 0x2
298-
elseif isconstType(result.result)
299-
rettype_const = result.result.parameters[1]
299+
elseif isconstType(res)
300+
rettype_const = res.parameters[1]
300301
const_flags = 0x2
301-
elseif isa(result.result, PartialStruct)
302-
rettype_const = (result.result::PartialStruct).fields
302+
elseif isa(res, PartialStruct)
303+
rettype_const = res.fields
304+
const_flags = 0x2
305+
elseif isa(res, InterConditional)
306+
rettype_const = res
303307
const_flags = 0x2
304308
else
305309
rettype_const = nothing
306310
const_flags = 0x00
307311
end
308312
end
309313
return CodeInstance(result.linfo,
310-
widenconst(result.result), rettype_const, inferred_result,
314+
widenconst(res), rettype_const, inferred_result,
311315
const_flags, first(valid_worlds), last(valid_worlds))
312316
end
313317

@@ -724,14 +728,18 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize
724728
code = get(code_cache(interp), mi, nothing)
725729
if code isa CodeInstance # return existing rettype if the code is already inferred
726730
update_valid_age!(caller, WorldRange(min_world(code), max_world(code)))
731+
rettype = code.rettype
727732
if isdefined(code, :rettype_const)
728-
if isa(code.rettype_const, Vector{Any}) && !(Vector{Any} <: code.rettype)
729-
return PartialStruct(code.rettype, code.rettype_const), mi
733+
rettype_const = code.rettype_const
734+
if isa(rettype_const, InterConditional)
735+
return rettype_const, mi
736+
elseif isa(rettype_const, Vector{Any}) && !(Vector{Any} <: rettype)
737+
return PartialStruct(rettype, rettype_const), mi
730738
else
731-
return Const(code.rettype_const), mi
739+
return Const(rettype_const), mi
732740
end
733741
else
734-
return code.rettype, mi
742+
return rettype, mi
735743
end
736744
end
737745
if ccall(:jl_get_module_infer, Cint, (Any,), method.module) == 0

base/compiler/typelattice.jl

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# structs/constants #
55
#####################
66

7-
# N.B.: Const/PartialStruct are defined in Core, to allow them to be used
7+
# N.B.: Const/PartialStruct/InterConditional are defined in Core, to allow them to be used
88
# inside the global code cache.
99
#
1010
# # The type of a value might be constant
@@ -18,7 +18,6 @@
1818
# end
1919
import Core: Const, PartialStruct
2020

21-
2221
# The type of this value might be Bool.
2322
# However, to enable a limited amount of back-propagagation,
2423
# we also keep some information about how this Bool value was created.
@@ -45,6 +44,18 @@ struct Conditional
4544
end
4645
end
4746

47+
# # Similar to `Conditional`, but conveys inter-procedural constraints imposed on call arguments.
48+
# # This is separate from `Conditional` to catch logic errors: the lattice element name is InterConditional
49+
# # while processing a call, then Conditional everywhere else. Thus InterConditional does not appear in
50+
# # CompilerTypes—these type's usages are disjoint—though we define the lattice for InterConditional.
51+
# struct InterConditional
52+
# slot::Int
53+
# vtype
54+
# elsetype
55+
# end
56+
import Core: InterConditional
57+
const AnyConditional = Union{Conditional,InterConditional}
58+
4859
struct PartialTypeVar
4960
tv::TypeVar
5061
# N.B.: Currently unused, but would allow turning something back
@@ -90,11 +101,10 @@ const CompilerTypes = Union{MaybeUndef, Const, Conditional, NotFound, PartialStr
90101
# lattice logic #
91102
#################
92103

93-
function issubconditional(a::Conditional, b::Conditional)
94-
avar = a.var
95-
bvar = b.var
96-
if (isa(avar, Slot) && isa(bvar, Slot) && slot_id(avar) === slot_id(bvar)) ||
97-
(isa(avar, SSAValue) && isa(bvar, SSAValue) && avar === bvar)
104+
# `Conditional` and `InterConditional` are valid in opposite contexts
105+
# (i.e. local inference and inter-procedural call), as such they will never be compared
106+
function issubconditional(a::C, b::C) where {C<:AnyConditional}
107+
if is_same_conditionals(a, b)
98108
if a.vtype b.vtype
99109
if a.elsetype b.elsetype
100110
return true
@@ -103,9 +113,11 @@ function issubconditional(a::Conditional, b::Conditional)
103113
end
104114
return false
105115
end
116+
is_same_conditionals(a::Conditional, b::Conditional) = slot_id(a.var) === slot_id(b.var)
117+
is_same_conditionals(a::InterConditional, b::InterConditional) = a.slot === b.slot
106118

107-
maybe_extract_const_bool(c::Const) = isa(c.val, Bool) ? c.val : nothing
108-
function maybe_extract_const_bool(c::Conditional)
119+
maybe_extract_const_bool(c::Const) = (val = c.val; isa(val, Bool)) ? val : nothing
120+
function maybe_extract_const_bool(c::AnyConditional)
109121
(c.vtype === Bottom && !(c.elsetype === Bottom)) && return false
110122
(c.elsetype === Bottom && !(c.vtype === Bottom)) && return true
111123
nothing
@@ -124,14 +136,14 @@ function ⊑(@nospecialize(a), @nospecialize(b))
124136
b === Union{} && return false
125137
@assert !isa(a, TypeVar) "invalid lattice item"
126138
@assert !isa(b, TypeVar) "invalid lattice item"
127-
if isa(a, Conditional)
128-
if isa(b, Conditional)
139+
if isa(a, AnyConditional)
140+
if isa(b, AnyConditional)
129141
return issubconditional(a, b)
130142
elseif isa(b, Const) && isa(b.val, Bool)
131143
return maybe_extract_const_bool(a) === b.val
132144
end
133145
a = Bool
134-
elseif isa(b, Conditional)
146+
elseif isa(b, AnyConditional)
135147
return false
136148
end
137149
if isa(a, PartialStruct)
@@ -205,7 +217,7 @@ function is_lattice_equal(@nospecialize(a), @nospecialize(b))
205217
return a b && b a
206218
end
207219

208-
widenconst(c::Conditional) = Bool
220+
widenconst(c::AnyConditional) = Bool
209221
function widenconst(c::Const)
210222
if isa(c.val, Type)
211223
if isvarargtype(c.val)
@@ -238,7 +250,7 @@ end
238250
@inline schanged(@nospecialize(n), @nospecialize(o)) = (n !== o) && (o === NOT_FOUND || (n !== NOT_FOUND && !issubstate(n, o)))
239251

240252
widenconditional(@nospecialize typ) = typ
241-
function widenconditional(typ::Conditional)
253+
function widenconditional(typ::AnyConditional)
242254
if typ.vtype === Union{}
243255
return Const(false)
244256
elseif typ.elsetype === Union{}

base/compiler/typelimits.jl

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ function tmerge(@nospecialize(typea), @nospecialize(typeb))
314314
end
315315
end
316316
if isa(typea, Conditional) && isa(typeb, Conditional)
317-
if typea.var === typeb.var
317+
if is_same_conditionals(typea, typeb)
318318
vtype = tmerge(typea.vtype, typeb.vtype)
319319
elsetype = tmerge(typea.elsetype, typeb.elsetype)
320320
if vtype != elsetype
@@ -327,6 +327,36 @@ function tmerge(@nospecialize(typea), @nospecialize(typeb))
327327
end
328328
return Bool
329329
end
330+
# type-lattice for InterConditional wrapper, InterConditional will never be merged with Conditional
331+
if isa(typea, InterConditional) && isa(typeb, Const)
332+
if typeb.val === true
333+
typeb = InterConditional(typea.slot, Any, Union{})
334+
elseif typeb.val === false
335+
typeb = InterConditional(typea.slot, Union{}, Any)
336+
end
337+
end
338+
if isa(typeb, InterConditional) && isa(typea, Const)
339+
if typea.val === true
340+
typea = InterConditional(typeb.slot, Any, Union{})
341+
elseif typea.val === false
342+
typea = InterConditional(typeb.slot, Union{}, Any)
343+
end
344+
end
345+
if isa(typea, InterConditional) && isa(typeb, InterConditional)
346+
if is_same_conditionals(typea, typeb)
347+
vtype = tmerge(typea.vtype, typeb.vtype)
348+
elsetype = tmerge(typea.elsetype, typeb.elsetype)
349+
if vtype != elsetype
350+
return InterConditional(typea.slot, vtype, elsetype)
351+
end
352+
end
353+
val = maybe_extract_const_bool(typea)
354+
if val isa Bool && val === maybe_extract_const_bool(typeb)
355+
return Const(val)
356+
end
357+
return Bool
358+
end
359+
# type-lattice for Const and PartialStruct wrappers
330360
if (isa(typea, PartialStruct) || isa(typea, Const)) &&
331361
(isa(typeb, PartialStruct) || isa(typeb, Const)) &&
332362
widenconst(typea) === widenconst(typeb)

src/builtins.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1608,6 +1608,7 @@ void jl_init_primitives(void) JL_GC_DISABLED
16081608
add_builtin("Argument", (jl_value_t*)jl_argument_type);
16091609
add_builtin("Const", (jl_value_t*)jl_const_type);
16101610
add_builtin("PartialStruct", (jl_value_t*)jl_partial_struct_type);
1611+
add_builtin("InterConditional", (jl_value_t*)jl_interconditional_type);
16111612
add_builtin("MethodMatch", (jl_value_t*)jl_method_match_type);
16121613
add_builtin("IntrinsicFunction", (jl_value_t*)jl_intrinsic_type);
16131614
add_builtin("Function", (jl_value_t*)jl_function_type);

src/jl_exported_data.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
XX(jl_nothing_type) \
7272
XX(jl_number_type) \
7373
XX(jl_partial_struct_type) \
74+
XX(jl_interconditional_type) \
7475
XX(jl_phicnode_type) \
7576
XX(jl_phinode_type) \
7677
XX(jl_pinode_type) \

src/jltypes.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2302,6 +2302,10 @@ void jl_init_types(void) JL_GC_DISABLED
23022302
jl_perm_symsvec(2, "typ", "fields"),
23032303
jl_svec2(jl_any_type, jl_array_any_type), 0, 0, 2);
23042304

2305+
jl_interconditional_type = jl_new_datatype(jl_symbol("InterConditional"), core, jl_any_type, jl_emptysvec,
2306+
jl_perm_symsvec(3, "slot", "vtype", "elsetype"),
2307+
jl_svec(3, jl_long_type, jl_any_type, jl_any_type), 0, 0, 3);
2308+
23052309
jl_method_match_type = jl_new_datatype(jl_symbol("MethodMatch"), core, jl_any_type, jl_emptysvec,
23062310
jl_perm_symsvec(4, "spec_types", "sparams", "method", "fully_covers"),
23072311
jl_svec(4, jl_type_type, jl_simplevector_type, jl_method_type, jl_bool_type), 0, 0, 4);

0 commit comments

Comments
 (0)