diff --git a/NEWS.md b/NEWS.md index 936ecd94..9a2128a5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,7 @@ - v0.1.5 - Patch for Julia PR [#20889](https://github.com/JuliaLang/julia/pull/20889), which changes how lowering is done for exponentiation of integer literals. - Bug fix to enable registering Main as a module for `u_str` (fixes [#61](https://github.com/ajkeller34/Unitful.jl/issues/61)). + - Implement readable message for `DimensionError` [#62](https://github.com/ajkeller34/Unitful.jl/pull/62). - v0.1.4 - Critical bug fix owing to `mod_fast` changes. - v0.1.3 diff --git a/src/Conversion.jl b/src/Conversion.jl index bd286cf9..92ab25be 100644 --- a/src/Conversion.jl +++ b/src/Conversion.jl @@ -29,7 +29,7 @@ function uconvert(a::Units, x::Number) if dimension(a) == NoDims Quantity(x * convfact(a, NoUnits), a) else - throw(DimensionError()) + throw(DimensionError(a,x)) end end @@ -47,7 +47,7 @@ Find the conversion factor from unit `t` to unit `s`, e.g. `convfact(m,cm) = 0.0 # Check if conversion is possible in principle sdim = dimension(s()) tdim = dimension(t()) - sdim != tdim && throw(DimensionError()) + sdim != tdim && throw(DimensionError(s(),t())) # first convert to base SI units. # fact1 is what would need to be multiplied to get to base SI units @@ -133,7 +133,7 @@ function convert{T,D,U}(::Type{Quantity{T,D,U}}, x::Number) if dimension(x) == D() Quantity(T(uconvert(U(),x).val), U()) else - throw(DimensionError()) + throw(DimensionError(U(),x)) end end @@ -179,7 +179,7 @@ function convert{T,U}(::Type{DimensionlessQuantity{T,U}}, x::Quantity) if dimension(x) == NoDims Quantity(T(x.val), U()) else - throw(DimensionError()) + throw(DimensionError(NoDims,x)) end end diff --git a/src/Promotion.jl b/src/Promotion.jl index a76a043d..06d0159d 100644 --- a/src/Promotion.jl +++ b/src/Promotion.jl @@ -7,7 +7,7 @@ if dimension(S())==dimension(T()) promote_type(S,T) else - throw(DimensionError()) + throw(DimensionError(S(),T())) end end end @@ -18,7 +18,7 @@ if dimension(S())==dimension(T()) promote_type(S,T) else - throw(DimensionError()) + throw(DimensionError(S(),T())) end end end @@ -35,7 +35,7 @@ ::Type{Quantity{T1,D1,U1}}, ::Type{Quantity{T2,D2,U2}}) # figuring out numeric type can be subtle if D1 == D2 but U1 != U2. # in particular, consider adding 1m + 1cm... the numtype is not Int. - D1 != D2 && throw(DimensionError()) + D1 != D2 && throw(DimensionError(U1(),U2())) return Bool end function promote_op{T1,D1,U1,T2,D2,U2}(op, x::Type{Quantity{T1,D1,U1}}, @@ -62,7 +62,7 @@ ::Type{R}, ::Type{Quantity{S,D,U}}) # figuring out numeric type can be subtle if D1 == D2 but U1 != U2. # in particular, consider adding 1m + 1cm... the numtype is not Int. - D != Dimensions{()} && throw(DimensionError()) + D != Dimensions{()} && throw(DimensionError(NoDims,U())) return Bool end function promote_op{R<:Number,S,D,U}(op, ::Type{R}, ::Type{Quantity{S,D,U}}) @@ -86,7 +86,7 @@ ::Type{Quantity{S,D,U}}, ::Type{R}) # figuring out numeric type can be subtle if D1 == D2 but U1 != U2. # in particular, consider adding 1m + 1cm... the numtype is not Int. - D != Dimensions{()} && throw(DimensionError()) + D != Dimensions{()} && throw(DimensionError(NoDims,U())) return Bool end function promote_op{R<:Number,S,D,U}(op, x::Type{Quantity{S,D,U}}, y::Type{R}) diff --git a/src/Unitful.jl b/src/Unitful.jl index 1fc125ce..b4b1a1c4 100644 --- a/src/Unitful.jl +++ b/src/Unitful.jl @@ -49,12 +49,21 @@ const NoDims = Dimensions{()}() """ ``` -type DimensionError <: Exception end +type DimensionError{T,S} <: Exception + x::T + y::S +end ``` Thrown when dimensions don't match in an operation that demands they do. +Display `x` and `y` in error message. """ -type DimensionError <: Exception end +type DimensionError{T,S} <: Exception + x::T + y::S +end +Base.showerror(io::IO, e::DimensionError) = + print(io,"DimensionError: $(e.x) and $(e.y) are not dimensionally compatible."); """ ``` @@ -403,19 +412,19 @@ for op in [:+, :-] :($($op)(uconvert($result_units, x), uconvert($result_units, y))) end - @eval ($op)(::Quantity, ::Quantity) = throw(DimensionError()) + @eval ($op)(x::Quantity, y::Quantity) = throw(DimensionError(x,y)) @eval function ($op)(x::Quantity, y::Number) if isa(x, DimensionlessQuantity) ($op)(promote(x,y)...) else - throw(DimensionError()) + throw(DimensionError(x,y)) end end @eval function ($op)(x::Number, y::Quantity) if isa(y, DimensionlessQuantity) ($op)(promote(x,y)...) else - throw(DimensionError()) + throw(DimensionError(x,y)) end end @@ -673,7 +682,7 @@ for (_x,_y) in [(:fma, :_fma), (:muladd, :_muladd)] # It seems like most of this is optimized out by the compiler, including the # apparent runtime check of dimensions, which does not appear in @code_llvm. @eval @inline function ($_y)(x,y,z) - dimension(x) * dimension(y) != dimension(z) && throw(DimensionError()) + dimension(x) * dimension(y) != dimension(z) && throw(DimensionError(x*y,z)) uI = unit(x)*unit(y) uF = promote_type(typeof(uI), typeof(unit(z)))() c = ($_x)(ustrip(x), ustrip(y), ustrip(uconvert(uI, z))) @@ -722,14 +731,14 @@ end atan2(y::Quantity, x::Quantity) = atan2(promote(y,x)...) atan2{T,D,U}(y::Quantity{T,D,U}, x::Quantity{T,D,U}) = atan2(y.val,x.val) atan2{T,D1,U1,D2,U2}(y::Quantity{T,D1,U1}, x::Quantity{T,D2,U2}) = - throw(DimensionError()) + throw(DimensionError(x,y)) for (f, F) in [(:min, :<), (:max, :>)] @eval @generated function ($f)(x::Quantity, y::Quantity) xdim = x.parameters[2]() ydim = y.parameters[2]() if xdim != ydim - return :(throw(DimensionError())) + return :(throw(DimensionError(x,y))) end xunits = x.parameters[3].parameters[1] @@ -768,7 +777,7 @@ flipsign(x::Quantity, y::Number) = Quantity(flipsign(x.val,y/unit(y)), unit(x)) @inline isless{T,D,U}(x::Quantity{T,D,U}, y::Quantity{T,D,U}) = _isless(x,y) @inline _isless{T,D,U}(x::Quantity{T,D,U}, y::Quantity{T,D,U}) = isless(x.val, y.val) -@inline _isless{T,D1,D2,U1,U2}(x::Quantity{T,D1,U1}, y::Quantity{T,D2,U2}) = throw(DimensionError()) +@inline _isless{T,D1,D2,U1,U2}(x::Quantity{T,D1,U1}, y::Quantity{T,D2,U2}) = throw(DimensionError(x,y)) @inline _isless(x,y) = isless(x,y) isless(x::Quantity, y::Quantity) = _isless(promote(x,y)...) @@ -777,7 +786,7 @@ isless(x::Number, y::Quantity) = _isless(promote(x,y)...) @inline <{T,D,U}(x::Quantity{T,D,U}, y::Quantity{T,D,U}) = _lt(x,y) @inline _lt{T,D,U}(x::Quantity{T,D,U}, y::Quantity{T,D,U}) = <(x.val,y.val) -@inline _lt{T,D1,D2,U1,U2}(x::Quantity{T,D1,U1}, y::Quantity{T,D2,U2}) = throw(DimensionError()) +@inline _lt{T,D1,D2,U1,U2}(x::Quantity{T,D1,U1}, y::Quantity{T,D2,U2}) = throw(DimensionError(x,y)) @inline _lt(x,y) = <(x,y) <(x::Quantity, y::Quantity) = _lt(promote(x,y)...) diff --git a/src/range.jl b/src/range.jl index d94cb8ff..1fb3c6a0 100644 --- a/src/range.jl +++ b/src/range.jl @@ -10,18 +10,18 @@ linspace(Float64, ustrip(start), ustrip(stop), len, 1)*unit(T) function _linspace{T}(start::Quantity{T}, stop::Quantity{T}, len::Integer) - dimension(start) != dimension(stop) && throw(DimensionError()) + dimension(start) != dimension(stop) && throw(DimensionError(start, stop)) linspace(start, stop, len) end @compat function colon(start::Quantity{<:Real}, step, stop::Quantity{<:Real}) - dimension(start) != dimension(stop) && throw(DimensionError()) + dimension(start) != dimension(stop) && throw(DimensionError(start, stop)) T = promote_type(typeof(start),typeof(stop)) return colon(convert(T,start), step, convert(T,stop)) end function colon(start::A, step::B, stop::A) where A<:Quantity{<:Real} where B<:Quantity{<:Real} - dimension(start) != dimension(step) && throw(DimensionError()) + dimension(start) != dimension(step) && throw(DimensionError(start, step)) colon(promote(start, step, stop)...) end diff --git a/test/runtests.jl b/test/runtests.jl index 9338e8ba..3d06e94b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -839,6 +839,17 @@ end end end +@testset "DimensionError message" begin + function errorstr(e) + b = IOBuffer() + Base.showerror(b,e) + String(b) + end + @test errorstr(DimensionError(1u"m",2)) == "DimensionError: 1 m and 2 are not dimensionally compatible." + @test errorstr(DimensionError(1u"m",NoDims)) == "DimensionError: 1 m and are not dimensionally compatible." + @test errorstr(DimensionError(u"m",2)) == "DimensionError: m and 2 are not dimensionally compatible." +end + # Test that the @u_str macro will find units in other modules. module ShadowUnits using Unitful