Skip to content

Commit 1cb1cb6

Browse files
committed
introduce custom exception types to improve error messages
1 parent 14bc5a1 commit 1cb1cb6

File tree

2 files changed

+75
-16
lines changed

2 files changed

+75
-16
lines changed

base/promotion.jl

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -327,26 +327,81 @@ promote_type(::Type{T}, ::Type{T}) where {T} = T
327327
promote_type(::Type{T}, ::Type{Bottom}) where {T} = T
328328
promote_type(::Type{Bottom}, ::Type{T}) where {T} = T
329329

330-
const _promote_type_binary_recursion_depth_limit_exception = let
331-
s = "`promote_type`: recursion depth limit reached, giving up; check for faulty/conflicting/missing `promote_rule` methods"
332-
ArgumentError(s)
330+
"""
331+
TypePromotionError <: Exception
332+
333+
Exception type thrown by [`promote_type`](@ref) when giving up for either of two reasons:
334+
335+
* Because bugs in the [`promote_rule`](@ref) logic which would have caused infinite recursion were detected.
336+
337+
* Because a threshold on the recursion depth was reached.
338+
339+
Check for bugs in the [`promote_rule`](@ref) logic if this exception gets thrown.
340+
"""
341+
struct TypePromotionError <: Exception
342+
T_initial::Type
343+
S_initial::Type
344+
T::Type
345+
S::Type
346+
ts::Type
347+
st::Type
348+
threshold_reached::Bool
333349
end
334-
const _promote_type_binary_detected_infinite_recursion_exception = let
335-
s = "`promote_type`: detected unbounded recursion caused by faulty `promote_rule` logic"
336-
ArgumentError(s)
350+
351+
function showerror(io::IO, e::TypePromotionError)
352+
print(io, "TypePromotionError: `promote_type(T, S)` failed: ")
353+
354+
desc = if e.threshold_reached
355+
"giving up because the recursion depth limit was reached: check for faulty/conflicting/missing `promote_rule` methods\n"
356+
else
357+
"detected unbounded recursion caused by faulty `promote_rule` logic\n"
358+
end
359+
360+
print(io, desc)
361+
362+
print(io, " initial `T`: ")
363+
show(io, e.T_initial)
364+
print(io, '\n')
365+
366+
print(io, " initial `S`: ")
367+
show(io, e.S_initial)
368+
print(io, '\n')
369+
370+
print(io, " next-to-last `T`: ")
371+
show(io, e.T)
372+
print(io, '\n')
373+
374+
print(io, " next-to-last `S`: ")
375+
show(io, e.S)
376+
print(io, '\n')
377+
378+
print(io, " last `T`: ")
379+
show(io, e.ts)
380+
print(io, '\n')
381+
382+
print(io, " last `S`: ")
383+
show(io, e.st)
384+
print(io, '\n')
385+
386+
nothing
337387
end
338-
function _promote_type_binary_err_giving_up()
388+
389+
function _promote_type_binary_err_giving_up(@nospecialize(T_initial::Type), @nospecialize(S_initial::Type), @nospecialize(T::Type), @nospecialize(S::Type), @nospecialize(ts::Type), @nospecialize(st::Type))
339390
@noinline
340-
throw(_promote_type_binary_recursion_depth_limit_exception)
391+
@_nospecializeinfer_meta
392+
throw(TypePromotionError(T_initial, S_initial, T, S, ts, st, true))
341393
end
342-
function _promote_type_binary_err_detected_infinite_recursion()
394+
function _promote_type_binary_err_detected_infinite_recursion(@nospecialize(T_initial::Type), @nospecialize(S_initial::Type), @nospecialize(T::Type), @nospecialize(S::Type), @nospecialize(ts::Type), @nospecialize(st::Type))
343395
@noinline
344-
throw(_promote_type_binary_detected_infinite_recursion_exception)
396+
@_nospecializeinfer_meta
397+
throw(TypePromotionError(T_initial, S_initial, T, S, ts, st, false))
345398
end
399+
346400
function _promote_type_binary_detect_loop(T::Type, S::Type, A::Type, B::Type)
347401
onesided(T::Type, S::Type, A::Type, B::Type) = _types_are_equal(T, A) && _types_are_equal(S, B)
348402
onesided(T, S, A, B) || onesided(T, S, B, A)
349403
end
404+
350405
macro _promote_type_binary_step()
351406
e = quote
352407
# Try promote_rule in both orders.
@@ -368,14 +423,18 @@ macro _promote_type_binary_step()
368423
if _promote_type_binary_detect_loop(T, S, ts, st)
369424
# This is not strictly necessary, as we already limit the recursion depth, but
370425
# makes for nicer UX.
371-
_promote_type_binary_err_detected_infinite_recursion()
426+
_promote_type_binary_err_detected_infinite_recursion(T_initial, S_initial, T, S, ts, st)
372427
end
373428
T = ts
374429
S = st
375430
end
376431
esc(e)
377432
end
433+
378434
function _promote_type_binary(T::Type, S::Type)
435+
T_initial = T
436+
S_initial = S
437+
379438
@_promote_type_binary_step
380439
@_promote_type_binary_step
381440
@_promote_type_binary_step
@@ -385,7 +444,7 @@ function _promote_type_binary(T::Type, S::Type)
385444
@_promote_type_binary_step
386445
@_promote_type_binary_step
387446

388-
_promote_type_binary_err_giving_up()
447+
_promote_type_binary_err_giving_up(T_initial, S_initial, T, S, ts, st)
389448
end
390449

391450
function promote_type(::Type{T}, ::Type{S}) where {T,S}

test/core.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2026,8 +2026,8 @@ g4731() = f4731()
20262026
struct Issue13193_Interval{T<:Number} <: Number end
20272027
Base.promote_rule(::Type{Issue13193_Interval{T}}, ::Type{Issue13193_Interval{S}}) where {T, S} = Issue13193_Interval{promote_type(T,S)}
20282028
Base.promote_rule(::Type{Issue13193_Interval{T}}, ::Type{S}) where {T, S<:Number} = Issue13193_Interval{promote_type(T,S)}
2029-
@test_throws ArgumentError promote_type(Issue13193_Interval{Int}, Issue13193_SIQuantity{Int})
2030-
@test_throws ArgumentError promote_type(Issue13193_SIQuantity{Int}, Issue13193_Interval{Int})
2029+
@test_throws Base.TypePromotionError promote_type(Issue13193_Interval{Int}, Issue13193_SIQuantity{Int})
2030+
@test_throws Base.TypePromotionError promote_type(Issue13193_SIQuantity{Int}, Issue13193_Interval{Int})
20312031
@test Base.Compiler.is_foldable(Base.infer_effects(promote_type, Tuple{Type{Issue13193_Interval{Int}}, Type{Issue13193_SIQuantity{Int}}}))
20322032
@test Base.Compiler.is_foldable(Base.infer_effects(promote_type, Tuple{Type{Issue13193_SIQuantity{Int}}, Type{Issue13193_Interval{Int}}}))
20332033
end
@@ -2036,8 +2036,8 @@ end
20362036
struct ConflictingPromoteRuleDefinitionsB end
20372037
Base.promote_rule(::Type{ConflictingPromoteRuleDefinitionsA}, ::Type{ConflictingPromoteRuleDefinitionsB}) = ConflictingPromoteRuleDefinitionsA
20382038
Base.promote_rule(::Type{ConflictingPromoteRuleDefinitionsB}, ::Type{ConflictingPromoteRuleDefinitionsA}) = ConflictingPromoteRuleDefinitionsB
2039-
@test_throws ArgumentError promote_type(ConflictingPromoteRuleDefinitionsA, ConflictingPromoteRuleDefinitionsB)
2040-
@test_throws ArgumentError promote_type(ConflictingPromoteRuleDefinitionsB, ConflictingPromoteRuleDefinitionsA)
2039+
@test_throws Base.TypePromotionError promote_type(ConflictingPromoteRuleDefinitionsA, ConflictingPromoteRuleDefinitionsB)
2040+
@test_throws Base.TypePromotionError promote_type(ConflictingPromoteRuleDefinitionsB, ConflictingPromoteRuleDefinitionsA)
20412041
@test Base.Compiler.is_foldable(Base.infer_effects(promote_type, Tuple{Type{ConflictingPromoteRuleDefinitionsA}, Type{ConflictingPromoteRuleDefinitionsB}}))
20422042
@test Base.Compiler.is_foldable(Base.infer_effects(promote_type, Tuple{Type{ConflictingPromoteRuleDefinitionsB}, Type{ConflictingPromoteRuleDefinitionsA}}))
20432043
end

0 commit comments

Comments
 (0)