Skip to content

Commit

Permalink
Add range(; stop) and range(; length) (JuliaLang#39241)
Browse files Browse the repository at this point in the history
As a single keyword arg only (single positional arg not allowed still)
  • Loading branch information
mkitti committed Aug 16, 2021
1 parent 3306a8b commit 5ff9e3a
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 3 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ New library features
Standard library changes
------------------------

* `range` accepts either `stop` or `length` as a sole keyword argument ([#39241])
* The `length` function on certain ranges of certain specific element types no longer checks for integer
overflow in most cases. The new function `checked_length` is now available, which will try to use checked
arithmetic to error if the result may be wrapping. Or use a package such as SaferIntegers.jl when
Expand Down
41 changes: 39 additions & 2 deletions base/range.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ Valid invocations of range are:
* Call `range` with any three of `start`, `step`, `stop`, `length`.
* Call `range` with two of `start`, `stop`, `length`. In this case `step` will be assumed
to be one. If both arguments are Integers, a [`UnitRange`](@ref) will be returned.
* Call `range` with one of `stop` or `length`. `start` and `step` will be assumed to be one.
See Extended Help for additional details on the returned type.
# Examples
```jldoctest
Expand Down Expand Up @@ -87,6 +90,15 @@ julia> range(stop=10, step=1, length=5)
julia> range(start=1, step=1, stop=10)
1:1:10
julia> range(; length = 10)
Base.OneTo(10)
julia> range(; stop = 6)
Base.OneTo(6)
julia> range(; stop = 6.5)
1.0:1.0:6.0
```
If `length` is not specified and `stop - start` is not an integer multiple of `step`, a range that ends before `stop` will be produced.
```jldoctest
Expand All @@ -103,6 +115,23 @@ To avoid this induced overhead, see the [`LinRange`](@ref) constructor.
!!! compat "Julia 1.7"
The versions without keyword arguments and `start` as a keyword argument
require at least Julia 1.7.
!!! compat "Julia 1.8"
The versions with `stop` as a sole keyword argument,
or `length` as a sole keyword argument require at least Julia 1.8.
# Extended Help
`range` will produce a `Base.OneTo` when the arguments are Integers and
* Only `length` is provided
* Only `stop` is provided
`range` will produce a `UnitRange` when the arguments are Integers and
* Only `start` and `stop` are provided
* Only `length` and `stop` are provided
A `UnitRange` is not produced if `step` is provided even if specified as one.
"""
function range end

Expand All @@ -115,8 +144,8 @@ range(;start=nothing, stop=nothing, length::Union{Integer, Nothing}=nothing, ste
_range(start, step, stop, length)

_range(start::Nothing, step::Nothing, stop::Nothing, len::Nothing) = range_error(start, step, stop, len)
_range(start::Nothing, step::Nothing, stop::Nothing, len::Any ) = range_error(start, step, stop, len)
_range(start::Nothing, step::Nothing, stop::Any , len::Nothing) = range_error(start, step, stop, len)
_range(start::Nothing, step::Nothing, stop::Nothing, len::Any ) = range_length(len)
_range(start::Nothing, step::Nothing, stop::Any , len::Nothing) = range_stop(stop)
_range(start::Nothing, step::Nothing, stop::Any , len::Any ) = range_stop_length(stop, len)
_range(start::Nothing, step::Any , stop::Nothing, len::Nothing) = range_error(start, step, stop, len)
_range(start::Nothing, step::Any , stop::Nothing, len::Any ) = range_error(start, step, stop, len)
Expand All @@ -131,6 +160,14 @@ _range(start::Any , step::Any , stop::Nothing, len::Any ) = range_start
_range(start::Any , step::Any , stop::Any , len::Nothing) = range_start_step_stop(start, step, stop)
_range(start::Any , step::Any , stop::Any , len::Any ) = range_error(start, step, stop, len)

# Length as the only argument
range_length(len::Integer) = OneTo(len)

# Stop as the only argument
range_stop(stop) = range_start_stop(oneunit(stop), stop)
range_stop(stop::Integer) = range_length(stop)

# Stop and length as the only argument
range_stop_length(a::Real, len::Integer) = UnitRange{typeof(a)}(oftype(a, a-len+1), a)
range_stop_length(a::AbstractFloat, len::Integer) = range_step_stop_length(oftype(a, 1), a, len)
range_stop_length(a, len::Integer) = range_step_stop_length(oftype(a-a, 1), a, len)
Expand Down
20 changes: 19 additions & 1 deletion test/ranges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ using Base.Checked: checked_length
# the next ones use ==, because it changes the eltype
@test r == range(first(r), last(r), length(r) )
@test r == range(start=first(r), stop=last(r), length=length(r))
@test r === range( stop=last(r), length=length(r))

r = 1:5
o = Base.OneTo(5)
let start=first(r), step=step(r), stop=last(r), length=length(r)
@test o === range(; stop )
@test o === range(; length)
@test r === range(; start, stop )
@test r === range(; stop, length)
# the next three lines uses ==, because it changes the eltype
@test r == range(; start, stop, length)
@test r == range(; start, step, length)
@test r == range(; stop=Float64(stop))
end

for T = (Int8, Rational{Int16}, UInt32, Float64, Char)
@test typeof(range(start=T(5), length=3)) === typeof(range(stop=T(5), length=3))
Expand Down Expand Up @@ -1508,8 +1522,12 @@ end
@test_throws ArgumentError range(1)
@test_throws ArgumentError range(nothing)
@test_throws ArgumentError range(1, step=4)
@test_throws ArgumentError range(nothing, length=2)
@test_throws ArgumentError range(; step=1, length=6)
@test_throws ArgumentError range(; step=2, stop=7.5)
@test_throws ArgumentError range(1.0, step=0.25, stop=2.0, length=5)
@test_throws ArgumentError range(; stop=nothing)
@test_throws ArgumentError range(; length=nothing)
@test_throws TypeError range(; length=5.5)
end

@testset "issue #23300#issuecomment-371575548" begin
Expand Down

0 comments on commit 5ff9e3a

Please sign in to comment.