Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ Build system changes
New library functions
---------------------

* a new abstract type for iterators with `HasShape{N}` is added: `ShapefulIterator{N}`. It supertypes existing abstract types:
* `AbstractArray`
* `AbstractChar`
* `Number`
* `Ref`
It's still acceptable to define a new shapeful iterator type that does not subtype `ShapefulIterator`, however choosing to subtype `ShapefulIterator{N}` means there's no need to add certain methods for one's newly-defined type.

New library features
--------------------

Expand Down
21 changes: 1 addition & 20 deletions base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Basic functions ##

"""
AbstractArray{T,N}
AbstractArray{T,N} <: ShapefulIterator{N}

Supertype for `N`-dimensional arrays (or array-like types) with elements of type `T`.
[`Array`](@ref) and other types are subtypes of this. See the manual section on the
Expand Down Expand Up @@ -256,25 +256,6 @@ julia> Base.elsize(rand(Float32, 10))
"""
elsize(A::AbstractArray) = elsize(typeof(A))

"""
ndims(A::AbstractArray)::Integer

Return the number of dimensions of `A`.

See also: [`size`](@ref), [`axes`](@ref).

# Examples
```jldoctest
julia> A = fill(1, (3,4,5));

julia> ndims(A)
3
```
"""
ndims(::AbstractArray{T,N}) where {T,N} = N::Int
ndims(::Type{<:AbstractArray{<:Any,N}}) where {N} = N::Int
ndims(::Type{Union{}}, slurp...) = throw(ArgumentError("Union{} does not have elements"))

"""
length(collection)::Integer

Expand Down
9 changes: 5 additions & 4 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
#end
#const nothing = Nothing()

#abstract type AbstractArray{T,N} end
#abstract type ShapefulIterator{N} end
#abstract type AbstractArray{T,N} <: ShapefulIterator{N} end
#abstract type DenseArray{T,N} <: AbstractArray{T,N} end

#primitive type AddrSpace{Backend::Module} 8 end
Expand Down Expand Up @@ -208,7 +209,7 @@ export
# key types
Any, DataType, Vararg, NTuple,
Tuple, Type, UnionAll, TypeVar, Union, Nothing, Cvoid,
AbstractArray, DenseArray, NamedTuple, Pair,
AbstractArray, DenseArray, NamedTuple, Pair, ShapefulIterator,
# special objects
Function, Method, Module, Symbol, Task, UndefInitializer, undef, WeakRef, VecElement,
Array, Memory, MemoryRef, AtomicMemory, AtomicMemoryRef, GenericMemory, GenericMemoryRef,
Expand Down Expand Up @@ -245,7 +246,7 @@ export
const getproperty = getfield # TODO: use `getglobal` for modules instead
const setproperty! = setfield!

abstract type Number end
abstract type Number <: ShapefulIterator{0} end
abstract type Real <: Number end
abstract type AbstractFloat <: Real end
abstract type Integer <: Real end
Expand All @@ -259,7 +260,7 @@ primitive type Float64 <: AbstractFloat 64 end
primitive type BFloat16 <: AbstractFloat 16 end

#primitive type Bool <: Integer 8 end
abstract type AbstractChar end
abstract type AbstractChar <: ShapefulIterator{0} end
primitive type Char <: AbstractChar 32 end

primitive type Int8 <: Signed 8 end
Expand Down
4 changes: 2 additions & 2 deletions base/broadcast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ to_index(Is::Tuple) = CartesianIndex(Is)

Index into `A` with `I`, collapsing broadcasted indices to their singleton indices as appropriate.
"""
Base.@propagate_inbounds _broadcast_getindex(A::Union{Ref,AbstractArray{<:Any,0},Number}, I) = A[] # Scalar-likes can just ignore all indices
Base.@propagate_inbounds _broadcast_getindex(A::ShapefulIterator{0}, I) = A[] # Scalar-likes can just ignore all indices
Base.@propagate_inbounds _broadcast_getindex(::Ref{Type{T}}, I) where {T} = T
# Tuples are statically known to be singleton or vector-like
Base.@propagate_inbounds _broadcast_getindex(A::Tuple{Any}, I) = A[1]
Expand Down Expand Up @@ -728,7 +728,7 @@ Base.RefValue{String}("hello")
"""
broadcastable(x::Union{Symbol,AbstractString,Function,UndefInitializer,Nothing,RoundingMode,Missing,Val,Ptr,AbstractPattern,Pair,IO,CartesianIndex}) = Ref(x)
broadcastable(::Type{T}) where {T} = Ref{Type{T}}(T)
broadcastable(x::Union{AbstractArray,Number,AbstractChar,Ref,Tuple,Broadcasted}) = x
broadcastable(x::Union{ShapefulIterator,Tuple,Broadcasted}) = x
# Default to collecting iterables — which will error for non-iterables
broadcastable(x) = collect(x)
broadcastable(::Union{AbstractDict, NamedTuple}) = throw(ArgumentError("broadcasting over dictionaries and `NamedTuple`s is reserved"))
Expand Down
5 changes: 2 additions & 3 deletions base/char.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import Core: AbstractChar, Char

"""
AbstractChar <: ShapefulIterator{0}

The `AbstractChar` type is the supertype of all character implementations
in Julia. A character represents a Unicode code point, and can be converted
to an integer via the [`codepoint`](@ref) function in order to obtain the
Expand Down Expand Up @@ -202,10 +204,7 @@ typemin(::Type{Char}) = bitcast(Char, typemin(UInt32))

size(c::AbstractChar) = ()
size(c::AbstractChar, d::Integer) = d < 1 ? throw(BoundsError()) : 1
ndims(c::AbstractChar) = 0
ndims(::Type{<:AbstractChar}) = 0
length(c::AbstractChar) = 1
IteratorSize(::Type{Char}) = HasShape{0}()
Copy link
Member Author

@nsajko nsajko Apr 9, 2025

Choose a reason for hiding this comment

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

NB: here IteratorSize was defined for Char. With this PR it's defined for all AbstractChar subtypes (making this kinda a minor change). However that's OK, because this method should have been IteratorSize(::Type{AbstractChar}) = HasShape{0}() in the first place. Refer to:

Copy link
Member Author

Choose a reason for hiding this comment

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

ndims being defined for AbstractChar implies IteratorSize should be treated the same, I'd say.

firstindex(c::AbstractChar) = 1
lastindex(c::AbstractChar) = 1
getindex(c::AbstractChar) = c
Expand Down
22 changes: 19 additions & 3 deletions base/docs/basedocs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ which are their descendants. Abstract types form the conceptual hierarchy which
Julia’s type system more than just a collection of object implementations. For example:

```julia
abstract type Number end
abstract type ShapefulIterator{N} end
abstract type Real <: Number end
```
[`Number`](@ref) has no supertype, whereas [`Real`](@ref) is an abstract subtype of `Number`.
[`ShapefulIterator`](@ref) has no supertype, whereas [`Real`](@ref) is an abstract subtype of `Number`.
"""
kw"abstract type", kw"abstract"

Expand Down Expand Up @@ -2184,7 +2184,23 @@ Stacktrace:
DivideError

"""
Number
ShapefulIterator{N}

`N`-dimensional [iterator](@ref man-interface-iteration) type, where `N isa Int`.

These functions have methods defined for all subtypes of `ShapefulIterator`, no need to add new methods - if you do add new methods they must be consistent with the following definitions:
* [`ndims`](@ref): returns `N`
* [`Base.IteratorSize`](@ref): returns `Base.HasShape{N}()`

New subtypes must implement:
* [`length`](@ref)
* [`size`](@ref)
* [`axes`](@ref), if the new subtype uses non-traditional indices
"""
ShapefulIterator

"""
Number <: ShapefulIterator{0}

Abstract supertype for all number types.
"""
Expand Down
2 changes: 1 addition & 1 deletion base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export Core,
# key types
Any, DataType, Vararg, NTuple,
Tuple, Type, UnionAll, TypeVar, Union, Nothing, Cvoid,
AbstractArray, DenseArray, NamedTuple, Pair,
AbstractArray, DenseArray, NamedTuple, Pair, ShapefulIterator,
# special objects
Function, Method, Module, Symbol, Task, UndefInitializer, undef, WeakRef, VecElement,
Array, Memory, MemoryRef, AtomicMemory, AtomicMemoryRef, GenericMemory, GenericMemoryRef,
Expand Down
21 changes: 20 additions & 1 deletion base/generator.jl
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,30 @@ IteratorSize(::Type{Union{}}, slurp...) = throw(ArgumentError("Union{} does not
IteratorSize(::Type{Any}) = SizeUnknown()

IteratorSize(::Type{<:Tuple}) = HasLength()
IteratorSize(::Type{<:AbstractArray{<:Any,N}}) where {N} = HasShape{N}()
IteratorSize(::Type{<:ShapefulIterator{N}}) where {N} = HasShape{N::Int}()
IteratorSize(::Type{Generator{I,F}}) where {I,F} = IteratorSize(I)

haslength(iter) = IteratorSize(iter) isa Union{HasShape, HasLength}

"""
ndims(A::ShapefulIterator)::Int

Return the number of dimensions of `A`.

See also: [`size`](@ref), [`axes`](@ref).

# Examples
```jldoctest
julia> A = fill(1, (3,4,5));

julia> ndims(A)
3
```
"""
ndims(::ShapefulIterator{N}) where {N} = N::Int
ndims(::Type{<:ShapefulIterator{N}}) where {N} = N::Int
ndims(::Type{Union{}}, slurp...) = throw(ArgumentError("Union{} does not have elements"))

abstract type IteratorEltype end
struct EltypeUnknown <: IteratorEltype end
struct HasEltype <: IteratorEltype end
Expand Down
3 changes: 0 additions & 3 deletions base/number.jl
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,11 @@ size(x::Number, d::Integer) = d < 1 ? throw(BoundsError()) : 1
axes(x::Number) = ()
axes(x::Number, d::Integer) = d < 1 ? throw(BoundsError()) : OneTo(1)
eltype(::Type{T}) where {T<:Number} = T
ndims(x::Number) = 0
ndims(::Type{<:Number}) = 0
length(x::Number) = 1
firstindex(x::Number) = 1
firstindex(x::Number, d::Int) = d < 1 ? throw(BoundsError()) : 1
lastindex(x::Number) = 1
lastindex(x::Number, d::Int) = d < 1 ? throw(BoundsError()) : 1
IteratorSize(::Type{<:Number}) = HasShape{0}()
keys(::Number) = OneTo(1)

getindex(x::Number) = x
Expand Down
5 changes: 1 addition & 4 deletions base/refpointer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import Core: Ref

"""
Ref{T}
Ref{T} <: ShapefulIterator{0}

An object that safely references data of type `T`. This type is guaranteed to point to
valid, Julia-allocated memory of the correct type. The underlying data is protected from
Expand Down Expand Up @@ -100,11 +100,8 @@ size(x::Ref) = ()
axes(x::Ref) = ()
length(x::Ref) = 1
isempty(x::Ref) = false
ndims(x::Ref) = 0
ndims(::Type{<:Ref}) = 0
iterate(r::Ref) = (r[], nothing)
iterate(r::Ref, s) = nothing
IteratorSize(::Type{<:Ref}) = HasShape{0}()

# create Ref objects for general object conversion
unsafe_convert(::Type{Ref{T}}, x::Ref{T}) where {T} = unsafe_convert(Ptr{T}, x)
Expand Down
2 changes: 2 additions & 0 deletions doc/src/base/collections.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@ Fully implemented by:
* `EachLine`
* [`AbstractString`](@ref)
* [`Set`](@ref)
* [`ShapefulIterator`](@ref)
* [`Pair`](@ref)
* [`NamedTuple`](@ref)

## Constructors and Types

```@docs
Base.ShapefulIterator
Base.AbstractRange
Base.OrdinalRange
Base.AbstractUnitRange
Expand Down
2 changes: 1 addition & 1 deletion doc/src/manual/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -1407,7 +1407,7 @@ julia> supertype(Float64)
AbstractFloat

julia> supertype(Number)
Any
ShapefulIterator{0}

julia> supertype(AbstractString)
Any
Expand Down
1 change: 1 addition & 0 deletions src/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -2570,6 +2570,7 @@ void jl_init_primitives(void) JL_GC_DISABLED
add_builtin("Ref", (jl_value_t*)jl_ref_type);
add_builtin("Ptr", (jl_value_t*)jl_pointer_type);
//add_builtin("GenericPtr", (jl_value_t*)jl_genericpointer_type);
add_builtin("ShapefulIterator", (jl_value_t*)jl_shapefuliterator_type);
add_builtin("AbstractArray", (jl_value_t*)jl_abstractarray_type);
add_builtin("DenseArray", (jl_value_t*)jl_densearray_type);
add_builtin("Array", (jl_value_t*)jl_array_type);
Expand Down
2 changes: 1 addition & 1 deletion src/ircode.c
Original file line number Diff line number Diff line change
Expand Up @@ -1611,7 +1611,7 @@ void jl_init_serializer(void)

jl_bool_type, jl_linenumbernode_type, jl_pinode_type,
jl_upsilonnode_type, jl_type_type, jl_bottom_type, jl_ref_type,
jl_pointer_type, jl_abstractarray_type, jl_nothing_type,
jl_pointer_type, jl_shapefuliterator_type, jl_abstractarray_type, jl_nothing_type,
jl_vararg_type,
jl_densearray_type, jl_function_type, jl_typename_type,
jl_builtin_type, jl_task_type, jl_uniontype_type,
Expand Down
1 change: 1 addition & 0 deletions src/jl_exported_data.inc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Pointers that are exposed through the public libjulia
#define JL_EXPORTED_DATA_POINTERS(XX) \
XX(jl_abioverride_type) \
XX(jl_shapefuliterator_type) \
XX(jl_abstractarray_type) \
XX(jl_abstractstring_type) \
XX(jl_addrspace_type) \
Expand Down
12 changes: 10 additions & 2 deletions src/jltypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -3300,9 +3300,16 @@ void jl_init_types(void) JL_GC_DISABLED
jl_addrspacecore_type = (jl_datatype_t*)jl_apply_type1((jl_value_t*)jl_addrspace_type, (jl_value_t*)jl_core_module);
jl_value_t *cpumem = jl_permbox8(jl_addrspacecore_type, 0, 0);

tv = jl_svec1(tvar("N"));
jl_shapefuliterator_type = (jl_unionall_t*)
jl_new_abstracttype((jl_value_t*)jl_symbol("ShapefulIterator"), core,
jl_any_type, tv)->name->wrapper;

tv = jl_svec1(tvar("T"));
jl_ref_type = (jl_unionall_t*)
jl_new_abstracttype((jl_value_t*)jl_symbol("Ref"), core, jl_any_type, tv)->name->wrapper;
jl_new_abstracttype((jl_value_t*)jl_symbol("Ref"), core,
(jl_datatype_t*)jl_apply_type1((jl_value_t*)jl_shapefuliterator_type, jl_box_long(0)),
tv)->name->wrapper;

tv = jl_svec1(tvar("T"));
jl_pointer_typename =
Expand All @@ -3317,7 +3324,8 @@ void jl_init_types(void) JL_GC_DISABLED
tv = jl_svec2(tvar("T"), tvar("N"));
jl_abstractarray_type = (jl_unionall_t*)
jl_new_abstracttype((jl_value_t*)jl_symbol("AbstractArray"), core,
jl_any_type, tv)->name->wrapper;
(jl_datatype_t*)jl_apply_type1((jl_value_t*)jl_shapefuliterator_type, jl_svecref(tv, 1)),
tv)->name->wrapper;

tv = jl_svec2(tvar("T"), tvar("N"));
jl_densearray_type = (jl_unionall_t*)
Expand Down
1 change: 1 addition & 0 deletions src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,7 @@ extern JL_DLLIMPORT jl_datatype_t *jl_module_type JL_GLOBALLY_ROOTED;
extern JL_DLLIMPORT jl_unionall_t *jl_addrspace_type JL_GLOBALLY_ROOTED;
extern JL_DLLIMPORT jl_typename_t *jl_addrspace_typename JL_GLOBALLY_ROOTED;
extern JL_DLLIMPORT jl_datatype_t *jl_addrspacecore_type JL_GLOBALLY_ROOTED;
extern JL_DLLIMPORT jl_unionall_t *jl_shapefuliterator_type JL_GLOBALLY_ROOTED;
extern JL_DLLIMPORT jl_unionall_t *jl_abstractarray_type JL_GLOBALLY_ROOTED;
extern JL_DLLIMPORT jl_unionall_t *jl_densearray_type JL_GLOBALLY_ROOTED;
extern JL_DLLIMPORT jl_unionall_t *jl_array_type JL_GLOBALLY_ROOTED;
Expand Down
3 changes: 2 additions & 1 deletion src/staticdata.c
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ extern "C" {
// TODO: put WeakRefs on the weak_refs list during deserialization
// TODO: handle finalizers

#define NUM_TAGS 197
#define NUM_TAGS 198

// An array of references that need to be restored from the sysimg
// This is a manually constructed dual of the gvars array, which would be produced by codegen for Julia code, for C.
Expand Down Expand Up @@ -169,6 +169,7 @@ jl_value_t **const*const get_tags(void) {
INSERT_TAG(jl_pointer_type);
INSERT_TAG(jl_llvmpointer_type);
INSERT_TAG(jl_vararg_type);
INSERT_TAG(jl_shapefuliterator_type);
INSERT_TAG(jl_abstractarray_type);
INSERT_TAG(jl_densearray_type);
INSERT_TAG(jl_nothing_type);
Expand Down
2 changes: 1 addition & 1 deletion stdlib/InteractiveUtils/src/InteractiveUtils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ See also [`subtypes`](@ref).
# Examples
```jldoctest
julia> supertypes(Int)
(Int64, Signed, Integer, Real, Number, Any)
(Int64, Signed, Integer, Real, Number, ShapefulIterator{0}, Any)
```
"""
function supertypes(T::Type)
Expand Down
2 changes: 1 addition & 1 deletion test/broadcast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@ end

@testset "scalar .= and promotion" begin
A = [[1, 2, 3], 4:5, 6]
@test A isa Vector{Any}
@test A isa Vector{ShapefulIterator}
A[1] .= 0
@test A[1] == [0, 0, 0]
@test_throws Base.CanonicalIndexError A[2] .= 0
Expand Down
4 changes: 2 additions & 2 deletions test/sets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,7 @@ end
f = replace(d, (1=>2) => (1=>missing), (3=>4)=>(3=>missing))
@test valtype(f) == Union{Missing,Int}
f = replace(d, (1=>2) => (1=>'a'), (3=>4)=>(3=>'b'))
@test valtype(f) == Any
@test valtype(f) == ShapefulIterator{0}
@test f == Dict(3=>'b', 1=>'a')

# eltype promotion for sets
Expand All @@ -900,7 +900,7 @@ end
@test f == Set([1, missing, nothing])
@test eltype(f) == Union{Int,Missing,Nothing}
f = replace(s, 2=>'a')
@test eltype(f) == Any
@test eltype(f) == ShapefulIterator{0}
@test f == Set([1, 3, 'a'])

# test that isequal is used
Expand Down
4 changes: 2 additions & 2 deletions test/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1506,8 +1506,8 @@ end

@test sprint(show, Main) == "Main"

@test sprint(Base.show_supertypes, Int64) == "Int64 <: Signed <: Integer <: Real <: Number <: Any"
@test sprint(Base.show_supertypes, Vector{String}) == "Vector{String} <: DenseVector{String} <: AbstractVector{String} <: Any"
@test sprint(Base.show_supertypes, Int64) == "Int64 <: Signed <: Integer <: Real <: Number <: ShapefulIterator{0} <: Any"
@test sprint(Base.show_supertypes, Vector{String}) == "Vector{String} <: DenseVector{String} <: AbstractVector{String} <: ShapefulIterator{1} <: Any"

# static_show

Expand Down
Loading