diff --git a/NEWS.md b/NEWS.md index cb298402ea88d2..b2e597bb1a3019 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 diff --git a/base/range.jl b/base/range.jl index 4ddecd7bdd91c1..1ab21c3d07d10a 100644 --- a/base/range.jl +++ b/base/range.jl @@ -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 @@ -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 @@ -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 @@ -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) @@ -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) diff --git a/test/ranges.jl b/test/ranges.jl index 1c31585ece45e0..958add3a9483b4 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -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)) @@ -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