Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Test.GenericDimensionful number type #39852

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ Standard library changes
------------------------

* `count` and `findall` now accept an `AbstractChar` argument to search for a character in a string ([#38675]).
* `Test` now exports a `GenericDimensionful` number type that can be used to test whether
code works with dimensionful types (like those in the Unitful.jl package) ([#39852]).
* `range` now supports the `range(start, stop)` and `range(start, stop, length)` methods ([#39228]).
* `range` now supports `start` as an optional keyword argument ([#38041]).
* `islowercase` and `isuppercase` are now compliant with the Unicode lower/uppercase categories ([#38574]).
Expand Down
8 changes: 7 additions & 1 deletion base/floatfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -278,9 +278,14 @@ true
function isapprox(x::Number, y::Number;
atol::Real=0, rtol::Real=rtoldefault(x,y,atol),
nans::Bool=false, norm::Function=abs)
x == y || (isfinite(x) && isfinite(y) && norm(x-y) <= max(atol, rtol*max(norm(x), norm(y)))) || (nans && isnan(x) && isnan(y))
x == y || (isfinite(x) && isfinite(y) && _isapprox_small(norm(x-y), atol, rtol, x, y, norm)) || (nans && isnan(x) && isnan(y))
end

# check if d is small compared to atol and rtol, ignoring the units (if any) of atol
# only if atol is zero.
stevengj marked this conversation as resolved.
Show resolved Hide resolved
_isapprox_small(d, atol, rtol, x, y, norm::F) where {F<:Function} =
iszero(atol) ? d <= rtol*max(norm(x), norm(y)) : d <= max(atol, rtol*max(norm(x), norm(y)))
stevengj marked this conversation as resolved.
Show resolved Hide resolved

"""
isapprox(x; kwargs...) / ≈(x; kwargs...)

Expand All @@ -301,6 +306,7 @@ This is equivalent to `!isapprox(x,y)` (see [`isapprox`](@ref)).
# default tolerance arguments
rtoldefault(::Type{T}) where {T<:AbstractFloat} = sqrt(eps(T))
rtoldefault(::Type{<:Real}) = 0
rtoldefault(::Type{T}) where {T<:Number} = rtoldefault(typeof(real(one(T)))) # strip dimensions if any
function rtoldefault(x::Union{T,Type{T}}, y::Union{S,Type{S}}, atol::Real) where {T<:Number,S<:Number}
rtol = max(rtoldefault(real(T)),rtoldefault(real(S)))
return atol > 0 ? zero(rtol) : rtol
Expand Down
4 changes: 2 additions & 2 deletions base/math.jl
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ function _hypot(x, y)

# Return Inf if either or both inputs is Inf (Compliance with IEEE754)
if isinf(ax) || isinf(ay)
return oftype(axu, Inf)
return typeof(axu)(Inf)
end

# Order the operands
Expand Down Expand Up @@ -661,7 +661,7 @@ _hypot(x::ComplexF16, y::ComplexF16) = Float16(_hypot(ComplexF32(x), ComplexF32(
function _hypot(x...)
maxabs = maximum(abs, x)
if isnan(maxabs) && any(isinf, x)
return oftype(maxabs, Inf)
return typeof(maxabs)(Inf)
elseif (iszero(maxabs) || isinf(maxabs))
return maxabs
else
Expand Down
10 changes: 5 additions & 5 deletions stdlib/LinearAlgebra/src/generic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -495,10 +495,10 @@ norm_sqr(x::Union{T,Complex{T},Rational{T}}) where {T<:Integer} = abs2(float(x))

function generic_norm2(x)
maxabs = normInf(x)
(maxabs == 0 || isinf(maxabs)) && return maxabs
(iszero(maxabs) || isinf(maxabs)) && return maxabs
(v, s) = iterate(x)::Tuple
T = typeof(maxabs)
if isfinite(length(x)*maxabs*maxabs) && maxabs*maxabs != 0 # Scaling not necessary
if isfinite(length(x)*maxabs*maxabs) && !iszero(maxabs*maxabs) # Scaling not necessary
sum::promote_type(Float64, T) = norm_sqr(v)
while true
y = iterate(x, s)
Expand All @@ -525,13 +525,13 @@ function generic_normp(x, p)
(v, s) = iterate(x)::Tuple
if p > 1 || p < -1 # might need to rescale to avoid overflow
maxabs = p > 1 ? normInf(x) : normMinusInf(x)
(maxabs == 0 || isinf(maxabs)) && return maxabs
(iszero(maxabs) || isinf(maxabs)) && return maxabs
T = typeof(maxabs)
else
T = typeof(float(norm(v)))
end
spp::promote_type(Float64, T) = p
if -1 <= p <= 1 || (isfinite(length(x)*maxabs^spp) && maxabs^spp != 0) # scaling not necessary
if -1 <= p <= 1 || (isfinite(length(x)*maxabs^spp) && !iszero(maxabs^spp)) # scaling not necessary
sum::promote_type(Float64, T) = norm(v)^spp
while true
y = iterate(x, s)
Expand Down Expand Up @@ -1659,7 +1659,7 @@ function isapprox(x::AbstractArray, y::AbstractArray;
nans::Bool=false, norm::Function=norm)
d = norm(x - y)
if isfinite(d)
return d <= max(atol, rtol*max(norm(x), norm(y)))
return Base._isapprox_small(d, atol, rtol, x, y, norm)
else
# Fall back to a component-wise approximate comparison
return all(ab -> isapprox(ab[1], ab[2]; rtol=rtol, atol=atol, nans=nans), zip(x, y))
Expand Down
9 changes: 9 additions & 0 deletions stdlib/LinearAlgebra/test/generic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -504,4 +504,13 @@ end
@test condskeel(A) ≈ condskeel(A, [8,8,8])
end

@testset "dimensionful arrays" begin
for p in (1,2,3,Inf), x in ([0,0,0], [1e300,2e300], [1,2,Inf])
@test GenericDimensionful(norm(x, p)) == norm(GenericDimensionful.(x), p)
end
@test dot(GenericDimensionful.([1,2,3]), [4,5,6]) == GenericDimensionful(32)
@test dot(GenericDimensionful.([1,2,3]), GenericDimensionful.([4,5,6])) == GenericDimensionful{2}(32)
@test GenericDimensionful.([1,0]) ≈ GenericDimensionful.([1,1e-13])
end

end # module TestGeneric
7 changes: 2 additions & 5 deletions stdlib/LinearAlgebra/test/givens.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,9 @@ end

# 36430
# dimensional correctness:
const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test")
isdefined(Main, :Furlongs) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "Furlongs.jl"))
using .Main.Furlongs

@testset "testing dimensions with Furlongs" begin
@test_throws MethodError givens(Furlong(1.0), Furlong(2.0), 1, 2)
@testset "testing dimensions with GenericDimensionfuls" begin
@test_throws MethodError givens(GenericDimensionful(1.0), GenericDimensionful(2.0), 1, 2)
end

const TNumber = Union{Float64,ComplexF64}
Expand Down
13 changes: 5 additions & 8 deletions stdlib/LinearAlgebra/test/lu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -325,19 +325,16 @@ include("trickyarithmetic.jl")
end

# dimensional correctness:
const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test")
isdefined(Main, :Furlongs) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "Furlongs.jl"))
using .Main.Furlongs

@testset "lu factorization with dimension type" begin
n = 4
A = Matrix(Furlong(1.0) * I, n, n)
A = Matrix(GenericDimensionful(1.0) * I, n, n)
F = lu(A).factors
@test Diagonal(F) == Diagonal(A)
# upper triangular part has a unit Furlong{1}
@test all(x -> typeof(x) == Furlong{1, Float64}, F[i,j] for j=1:n for i=1:j)
# lower triangular part is unitless Furlong{0}
@test all(x -> typeof(x) == Furlong{0, Float64}, F[i,j] for j=1:n for i=j+1:n)
# upper triangular part has a unit GenericDimensionful{1}
@test all(x -> typeof(x) == GenericDimensionful{1, Float64}, F[i,j] for j=1:n for i=1:j)
# lower triangular part is unitless GenericDimensionful{0}
@test all(x -> typeof(x) == GenericDimensionful{0, Float64}, F[i,j] for j=1:n for i=j+1:n)
end

@testset "Issue #30917. Determinant of integer matrix" begin
Expand Down
14 changes: 6 additions & 8 deletions stdlib/LinearAlgebra/test/special.jl
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,6 @@ end


# for testing types with a dimension
const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test")
isdefined(Main, :Furlongs) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "Furlongs.jl"))
using .Main.Furlongs

@testset "zero and one for structured matrices" begin
for elty in (Int64, Float64, ComplexF64)
Expand Down Expand Up @@ -395,11 +392,12 @@ using .Main.Furlongs
@test one(S) isa SymTridiagonal

# eltype with dimensions
D = Diagonal{Furlong{2, Int64}}([1, 2, 3, 4])
Bu = Bidiagonal{Furlong{2, Int64}}([1, 2, 3, 4], [1, 2, 3], 'U')
Bl = Bidiagonal{Furlong{2, Int64}}([1, 2, 3, 4], [1, 2, 3], 'L')
T = Tridiagonal{Furlong{2, Int64}}([1, 2, 3], [1, 2, 3, 4], [1, 2, 3])
S = SymTridiagonal{Furlong{2, Int64}}([1, 2, 3, 4], [1, 2, 3])
GD2 = GenericDimensionful{2}
D = Diagonal(GD2.([1, 2, 3, 4]))
Bu = Bidiagonal(GD2.([1, 2, 3, 4]), GD2.([1, 2, 3]), 'U')
Bl = Bidiagonal(GD2.([1, 2, 3, 4]), GD2.([1, 2, 3]), 'L')
T = Tridiagonal(GD2.([1, 2, 3]), GD2.([1, 2, 3, 4]), GD2.([1, 2, 3]))
S = SymTridiagonal(GD2.([1, 2, 3, 4]), GD2.([1, 2, 3]))
mats = [D, Bu, Bl, T, S]
for A in mats
@test iszero(zero(A))
Expand Down
9 changes: 3 additions & 6 deletions stdlib/LinearAlgebra/test/triangular.jl
Original file line number Diff line number Diff line change
Expand Up @@ -611,13 +611,10 @@ end
@test UpperTriangular(Matrix(1.0I, 3, 3)) \ view(fill(1., 3), [1,2,3]) == fill(1., 3)

# dimensional correctness:
const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test")
isdefined(Main, :Furlongs) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "Furlongs.jl"))
using .Main.Furlongs
LinearAlgebra.sylvester(a::Furlong,b::Furlong,c::Furlong) = -c / (a + b)
LinearAlgebra.sylvester(a::GenericDimensionful,b::GenericDimensionful,c::GenericDimensionful) = -c / (a + b)

let A = UpperTriangular([Furlong(1) Furlong(4); Furlong(0) Furlong(1)])
@test sqrt(A) == Furlong{1//2}.(UpperTriangular([1 2; 0 1]))
let A = UpperTriangular([GenericDimensionful(1) GenericDimensionful(4); GenericDimensionful(0) GenericDimensionful(1)])
@test sqrt(A) == GenericDimensionful{1//2}.(UpperTriangular([1 2; 0 1]))
end

@testset "similar should preserve underlying storage type" begin
Expand Down
16 changes: 16 additions & 0 deletions stdlib/Test/docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,19 @@ end
```@meta
DocTestSetup = nothing
```

## Generic Types

It is often useful to test that packages function correctly with string, container, and number types
that are similar but not identical to Julia `Base` types like `String`, `Array`, and so on. The
`Test` module exports a few such types for testing purposes, so that you don't need to have a test
dependency on a particular choice of external package implementing an alternative type:

```@docs
Test.GenericDimensionful
Test.GenericString
Test.GenericArray
Test.GenericDict
Test.GenericSet
Test.GenericOrder
```
109 changes: 109 additions & 0 deletions stdlib/Test/src/GenericDimensionfuls.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

module GenericDimensionfuls

export GenericDimensionful

# Here we implement a minimal dimensionful type GenericDimensionful, which is used
# to test dimensional correctness of various functions in Base.

"""
The `GenericDimensionful` type can be used to test that numerical code works with
dimensionful quantities. Construct a dimensionful quantity with `GenericDimensionful(x)`
(or `GenericDimensionful{p}(x)` for unitsᵖ). Arithmetic operations for `GenericDimensionful`
are defined so that you can only combine quantities in ways that respect the units.
"""
struct GenericDimensionful{p,T<:Number} <: Number
val::T
GenericDimensionful{p,T}(v::Number) where {p,T} = new(v)
stevengj marked this conversation as resolved.
Show resolved Hide resolved
end
GenericDimensionful(x::T) where {T<:Number} = GenericDimensionful{1,T}(x)
GenericDimensionful(x::GenericDimensionful) = x
(::Type{T})(x::GenericDimensionful) where {T<:Number} = T(x.val)::T
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(::Type{T})(x::GenericDimensionful) where {T<:Number} = T(x.val)::T
(::Type{T})(x::GenericDimensionful{0}) where {T<:Number} = T(x.val)::T

So that we don’t convert dimensionful numbers to dimensionless ones.

GenericDimensionful{p}(v::Number) where {p} = GenericDimensionful{p,typeof(v)}(v)
GenericDimensionful{p}(x::GenericDimensionful{q}) where {p,q} = GenericDimensionful{p,typeof(x.val)}(x.val)
GenericDimensionful{p,T}(x::GenericDimensionful{q}) where {T,p,q} = GenericDimensionful{p,T}(T(x.val))

Base.convert(::Type{GenericDimensionful{p}}, x::GenericDimensionful{p}) where {p} = x
Base.convert(::Type{GenericDimensionful{p,T}}, x::GenericDimensionful{p}) where {p,T} = GenericDimensionful{p,T}(x)
Base.convert(::Type{GenericDimensionful{0}}, x::Union{Real,Complex}) = GenericDimensionful{0}(x)
Base.convert(::Type{GenericDimensionful{0,T}}, x::Union{Real,Complex}) where {T} = GenericDimensionful(convert(T, x))
stevengj marked this conversation as resolved.
Show resolved Hide resolved
Base.convert(D::Type{GenericDimensionful{p}}, x::Number) where {p} = error("dimension mismatch between $D and $(typeof(x))")
Base.convert(D::Type{GenericDimensionful{p,T}}, x::Number) where {p,T} = error("dimension mismatch between $D and $(typeof(x))")

Base.promote_type(::Type{GenericDimensionful{p,T}}, ::Type{GenericDimensionful{p,S}}) where {p,T,S} =
(Base.@_pure_meta; GenericDimensionful{p,promote_type(T,S)})

Base.one(x::GenericDimensionful{p,T}) where {p,T} = one(T)
Base.one(::Type{GenericDimensionful{p,T}}) where {p,T} = one(T)
Base.oneunit(x::GenericDimensionful{p,T}) where {p,T} = GenericDimensionful{p,T}(one(T))
Base.oneunit(x::Type{GenericDimensionful{p,T}}) where {p,T} = GenericDimensionful{p,T}(one(T))
stevengj marked this conversation as resolved.
Show resolved Hide resolved
Base.zero(x::GenericDimensionful{p,T}) where {p,T} = GenericDimensionful{p,T}(zero(T))
Base.zero(::Type{GenericDimensionful{p,T}}) where {p,T} = GenericDimensionful{p,T}(zero(T))
Base.iszero(x::GenericDimensionful) = iszero(x.val)
Base.float(x::GenericDimensionful{p}) where {p} = GenericDimensionful{p}(float(x.val))
Base.eps(::Type{GenericDimensionful{p,T}}) where {p,T<:AbstractFloat} = eps(T) # relative precision is dimensionless
Base.eps(x::GenericDimensionful{p,T}) where {p,T<:AbstractFloat} = GenericDimensionful{p,T}(eps(x.val))
Base.floatmin(::Type{GenericDimensionful{p,T}}) where {p,T<:AbstractFloat} = GenericDimensionful{p}(floatmin(T))
Base.floatmin(::GenericDimensionful{p,T}) where {p,T<:AbstractFloat} = floatmin(GenericDimensionful{p,T})
Base.floatmax(::Type{GenericDimensionful{p,T}}) where {p,T<:AbstractFloat} = GenericDimensionful{p}(floatmax(T))
Base.floatmax(::GenericDimensionful{p,T}) where {p,T<:AbstractFloat} = floatmax(GenericDimensionful{p,T})

# convert GenericDimensionful exponent p to a canonical form. This
# is not type stable, but it doesn't matter since it is used
# at compile time (in generated functions), not runtime
canonical_p(p) = isinteger(p) ? Int(p) : Rational{Int}(p)

@generated Base.abs2(x::GenericDimensionful{p}) where {p} = :(GenericDimensionful{$(canonical_p(2p))}(abs2(x.val)))
@generated Base.inv(x::GenericDimensionful{p}) where {p} = :(GenericDimensionful{$(canonical_p(-p))}(inv(x.val)))

for f in (:isfinite, :isnan, :isreal, :isinf)
stevengj marked this conversation as resolved.
Show resolved Hide resolved
@eval Base.$f(x::GenericDimensionful) = $f(x.val)
end
for f in (:abs,:conj,:real,:imag,:complex,:+,:-)
@eval Base.$f(x::GenericDimensionful{p}) where {p} = GenericDimensionful{p}($f(x.val))
end

import Base: +, -, ==, !=, <, <=, isless, isequal, *, /, //, div, rem, mod, ^
for op in (:+, :-)
@eval function $op(x::GenericDimensionful{p}, y::GenericDimensionful{p}) where {p}
v = $op(x.val, y.val)
GenericDimensionful{p}(v)
end
end
for op in (:(==), :(!=), :<, :<=, :isless, :isequal)
@eval $op(x::GenericDimensionful{p}, y::GenericDimensionful{p}) where {p} = $op(x.val, y.val)
end
# generated functions to allow type inference of the value of the exponent:
for (f,op) in ((:_plus,:+),(:_minus,:-),(:_times,:*),(:_div,://))
@eval @generated function $f(v::T, ::GenericDimensionful{p}, ::Union{GenericDimensionful{q},Val{q}}) where {T,p,q}
s = $op(p, q)
:(GenericDimensionful{$(canonical_p(s)),$T}(v))
end
end
for (op,eop) in ((:*, :_plus), (:/, :_minus), (://, :_minus), (:div, :_minus))
@eval begin
$op(x::GenericDimensionful{p}, y::GenericDimensionful{q}) where {p,q} =
$eop($op(x.val, y.val),x,y)
$op(x::GenericDimensionful{p}, y::S) where {p,S<:Number} = $op(x,GenericDimensionful{0,S}(y))
$op(x::S, y::GenericDimensionful{p}) where {p,S<:Number} = $op(GenericDimensionful{0,S}(x),y)
end
end
# to fix an ambiguity
//(x::GenericDimensionful, y::Complex) = x // GenericDimensionful{0,typeof(y)}(y)
for op in (:rem, :mod)
@eval begin
$op(x::GenericDimensionful{p}, y::GenericDimensionful) where {p} = GenericDimensionful{p}($op(x.val, y.val))
$op(x::GenericDimensionful{p}, y::Number) where {p} = GenericDimensionful{p}($op(x.val, y))
stevengj marked this conversation as resolved.
Show resolved Hide resolved
end
end
Base.sqrt(x::GenericDimensionful) = _div(sqrt(x.val), x, Val(2))

@generated Base.literal_pow(::typeof(^), x::GenericDimensionful{p}, ::Val{q}) where {p,q} = :(GenericDimensionful{$(canonical_p(p*q))}(x.val^$q))
^(x::GenericDimensionful{p}, q::Real) where {p} = GenericDimensionful{p*q}(x.val^q)
^(x::GenericDimensionful{p}, q::Integer) where {p} = GenericDimensionful{p*q}(x.val^q) # fixes ambiguity
^(x::GenericDimensionful{p}, q::Rational) where {p} = GenericDimensionful{p*q}(x.val^q) # fixes ambiguity
^(x::GenericDimensionful{p}, q::GenericDimensionful{0}) where {p} = GenericDimensionful{p*q.val}(x.val^q.val)
^(x::GenericDimensionful{p}, q::GenericDimensionful) where {p} = error("exponent $(typeof(q)) is not dimensionless")

end
5 changes: 4 additions & 1 deletion stdlib/Test/src/Test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export @test, @test_throws, @test_broken, @test_skip,
export @testset
export @inferred
export detect_ambiguities, detect_unbound_args
export GenericString, GenericSet, GenericDict, GenericArray, GenericOrder
export GenericDimensionful, GenericString, GenericSet, GenericDict, GenericArray, GenericOrder
export TestSetException

using Random
Expand Down Expand Up @@ -1616,6 +1616,9 @@ function has_unbound_vars(@nospecialize sig)
end


include("GenericDimensionfuls.jl")
using .GenericDimensionfuls

"""
The `GenericString` can be used to test generic string APIs that program to
the `AbstractString` interface, in order to ensure that functions can work
Expand Down
21 changes: 3 additions & 18 deletions test/arrayops.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2072,24 +2072,9 @@ end
@test_throws ArgumentError LinearAlgebra.copy_transpose!(a,2:3,1:3,b,1:5,2:7)
end

module RetTypeDecl
using Test
import Base: +, *, broadcast, convert

struct MeterUnits{T,P} <: Number
val::T
end
MeterUnits(val::T, pow::Int) where {T} = MeterUnits{T,pow}(val)

m = MeterUnits(1.0, 1) # 1.0 meter, i.e. units of length
m2 = MeterUnits(1.0, 2) # 1.0 meter^2, i.e. units of area

(+)(x::MeterUnits{T,pow}, y::MeterUnits{T,pow}) where {T,pow} = MeterUnits{T,pow}(x.val+y.val)
(*)(x::Int, y::MeterUnits{T,pow}) where {T,pow} = MeterUnits{typeof(x*one(T)),pow}(x*y.val)
(*)(x::MeterUnits{T,1}, y::MeterUnits{T,1}) where {T} = MeterUnits{T,2}(x.val*y.val)
broadcast(::typeof(*), x::MeterUnits{T,1}, y::MeterUnits{T,1}) where {T} = MeterUnits{T,2}(x.val*y.val)
convert(::Type{MeterUnits{T,pow}}, y::Real) where {T,pow} = MeterUnits{T,pow}(convert(T,y))

@testset "dimensionful broadcasting" begin
m = GenericDimensionful(1.0)
m2 = m^2
@test @inferred(m .+ [m,m]) == [m+m,m+m]
@test @inferred([m,m] .+ m) == [m+m,m+m]
@test @inferred(broadcast(*,m,[m,m])) == [m2,m2]
Expand Down
Loading