• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #38002

06 Feb 2025 06:14AM UTC coverage: 20.322% (-2.4%) from 22.722%
#38002

push

local

web-flow
bpart: Fully switch to partitioned semantics (#57253)

This is the final PR in the binding partitions series (modulo bugs and
tweaks), i.e. it closes #54654 and thus closes #40399, which was the
original design sketch.

This thus activates the full designed semantics for binding partitions,
in particular allowing safe replacement of const bindings. It in
particular allows struct redefinitions. This thus closes
timholy/Revise.jl#18 and also closes #38584.

The biggest semantic change here is probably that this gets rid of the
notion of "resolvedness" of a binding. Previously, a lot of the behavior
of our implementation depended on when bindings were "resolved", which
could happen at basically an arbitrary point (in the compiler, in REPL
completion, in a different thread), making a lot of the semantics around
bindings ill- or at least implementation-defined. There are several
related issues in the bugtracker, so this closes #14055 closes #44604
closes #46354 closes #30277

It is also the last step to close #24569.
It also supports bindings for undef->defined transitions and thus closes
#53958 closes #54733 - however, this is not activated yet for
performance reasons and may need some further optimization.

Since resolvedness no longer exists, we need to replace it with some
hopefully more well-defined semantics. I will describe the semantics
below, but before I do I will make two notes:

1. There are a number of cases where these semantics will behave
slightly differently than the old semantics absent some other task going
around resolving random bindings.
2. The new behavior (except for the replacement stuff) was generally
permissible under the old semantics if the bindings happened to be
resolved at the right time.

With all that said, there are essentially three "strengths" of bindings:

1. Implicit Bindings: Anything implicitly obtained from `using Mod`, "no
binding", plus slightly more exotic corner cases around conflicts

2. Weakly declared bindin... (continued)

11 of 111 new or added lines in 7 files covered. (9.91%)

1273 existing lines in 68 files now uncovered.

9908 of 48755 relevant lines covered (20.32%)

105126.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

52.7
/base/strings/substring.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
"""
4
    SubString(s::AbstractString, i::Integer, j::Integer=lastindex(s))
5
    SubString(s::AbstractString, r::UnitRange{<:Integer})
6

7
Like [`getindex`](@ref), but returns a view into the parent string `s`
8
within range `i:j` or `r` respectively instead of making a copy.
9

10
The [`@views`](@ref) macro converts any string slices `s[i:j]` into
11
substrings `SubString(s, i, j)` in a block of code.
12

13
# Examples
14
```jldoctest
15
julia> SubString("abc", 1, 2)
16
"ab"
17

18
julia> SubString("abc", 1:2)
19
"ab"
20

21
julia> SubString("abc", 2)
22
"bc"
23
```
24
"""
25
struct SubString{T<:AbstractString} <: AbstractString
26
    string::T
27
    offset::Int
28
    ncodeunits::Int
29

30
    function SubString{T}(s::T, i::Int, j::Int) where T<:AbstractString
353✔
31
        i ≤ j || return new(s, 0, 0)
376✔
32
        @boundscheck begin
330✔
33
            checkbounds(s, i:j)
330✔
34
            @inbounds isvalid(s, i) || string_index_err(s, i)
330✔
35
            @inbounds isvalid(s, j) || string_index_err(s, j)
330✔
36
        end
37
        return new(s, i-1, nextind(s,j)-i)
330✔
38
    end
39
    function SubString{T}(s::T, i::Int, j::Int, ::Val{:noshift}) where T<:AbstractString
×
40
        @boundscheck if !(i == j == 0)
×
41
            si, sj = i + 1, prevind(s, j + i + 1)
×
42
            @inbounds isvalid(s, si) || string_index_err(s, si)
×
43
            @inbounds isvalid(s, sj) || string_index_err(s, sj)
×
44
        end
45
        new(s, i, j)
×
46
    end
47
end
48

49
@propagate_inbounds SubString(s::T, i::Int, j::Int) where {T<:AbstractString} = SubString{T}(s, i, j)
45,623✔
50
@propagate_inbounds SubString(s::T, i::Int, j::Int, v::Val{:noshift}) where {T<:AbstractString} = SubString{T}(s, i, j, v)
×
51
@propagate_inbounds SubString(s::AbstractString, i::Integer, j::Integer=lastindex(s)) = SubString(s, Int(i), Int(j))
341✔
52
@propagate_inbounds SubString(s::AbstractString, r::AbstractUnitRange{<:Integer}) = SubString(s, first(r), last(r))
39,495✔
53

54
@propagate_inbounds function SubString(s::SubString, i::Int, j::Int)
55
    @boundscheck i ≤ j && checkbounds(s, i:j)
5,663✔
56
    SubString(s.string, s.offset+i, s.offset+j)
5,663✔
57
end
58

59
SubString(s::AbstractString) = SubString(s, 1, lastindex(s)::Int)
×
60
SubString{T}(s::T) where {T<:AbstractString} = SubString{T}(s, 1, lastindex(s)::Int)
×
61

62
@propagate_inbounds view(s::AbstractString, r::AbstractUnitRange{<:Integer}) = SubString(s, r)
×
63
@propagate_inbounds maybeview(s::AbstractString, r::AbstractUnitRange{<:Integer}) = view(s, r)
×
64
@propagate_inbounds maybeview(s::AbstractString, args...) = getindex(s, args...)
×
65

66
convert(::Type{SubString{S}}, s::AbstractString) where {S<:AbstractString} =
×
67
    SubString(convert(S, s))::SubString{S}
68
convert(::Type{T}, s::T) where {T<:SubString} = s
×
69

70
# Regex match allows only Union{String, SubString{String}} so define conversion to this type
71
convert(::Type{Union{String, SubString{String}}}, s::String) = s
×
72
convert(::Type{Union{String, SubString{String}}}, s::SubString{String}) = s
×
73
convert(::Type{Union{String, SubString{String}}}, s::AbstractString) = convert(String, s)::String
×
74

75
function String(s::SubString{String})
76
    parent = s.string
228,926✔
77
    copy = GC.@preserve parent unsafe_string(pointer(parent, s.offset+1), s.ncodeunits)
228,926✔
78
    return copy
228,926✔
79
end
80

81
ncodeunits(s::SubString) = s.ncodeunits
1,449,343✔
82
codeunit(s::SubString) = codeunit(s.string)::CodeunitType
×
83
length(s::SubString) = length(s.string, s.offset+1, s.offset+s.ncodeunits)
×
84

85
function codeunit(s::SubString, i::Integer)
86
    @boundscheck checkbounds(s, i)
5,701✔
87
    @inbounds return codeunit(s.string, s.offset + i)
5,701✔
88
end
89

90
function iterate(s::SubString, i::Integer=firstindex(s))
91
    i == ncodeunits(s)+1 && return nothing
25,057✔
92
    @boundscheck checkbounds(s, i)
18,967✔
93
    y = iterate(s.string, s.offset + i)
37,934✔
94
    y === nothing && return nothing
18,967✔
95
    c, i = y::Tuple{AbstractChar,Int}
18,967✔
96
    return c, i - s.offset
18,967✔
97
end
98

99
function getindex(s::SubString, i::Integer)
100
    @boundscheck checkbounds(s, i)
15,205✔
101
    @inbounds return getindex(s.string, s.offset + i)
30,409✔
102
end
103

104
isascii(ss::SubString{String}) = isascii(codeunits(ss))
×
105

106
function isvalid(s::SubString, i::Integer)
107
    ib = true
×
108
    @boundscheck ib = checkbounds(Bool, s, i)
3,580✔
109
    @inbounds return ib && isvalid(s.string, s.offset + i)::Bool
7,160✔
110
end
111

112
thisind(s::SubString{String}, i::Int) = _thisind_str(s, i)
11,094✔
113
nextind(s::SubString{String}, i::Int) = _nextind_str(s, i)
12✔
114

115
parent(s::SubString) = s.string
×
116
parentindices(s::SubString) = (s.offset + 1 : thisind(s.string, s.offset + s.ncodeunits),)
×
117

118
function ==(a::Union{String, SubString{String}}, b::Union{String, SubString{String}})
119
    sizeof(a) == sizeof(b) && _memcmp(a, b) == 0
553,615✔
120
end
121

122
function cmp(a::SubString{String}, b::SubString{String})
×
123
    c = _memcmp(a, b)
×
124
    return c < 0 ? -1 : c > 0 ? +1 : cmp(sizeof(a), sizeof(b))
×
125
end
126

127
# don't make unnecessary copies when passing substrings to C functions
128
cconvert(::Type{Ptr{UInt8}}, s::SubString{String}) = s
×
129
cconvert(::Type{Ptr{Int8}}, s::SubString{String}) = s
×
130

131
function unsafe_convert(::Type{Ptr{R}}, s::SubString{String}) where R<:Union{Int8, UInt8}
132
    convert(Ptr{R}, pointer(s.string)) + s.offset
431,660✔
133
end
134

135
pointer(x::SubString{String}) = pointer(x.string) + x.offset
4,273✔
136
pointer(x::SubString{String}, i::Integer) = pointer(x.string) + x.offset + (i-1)
×
137

138
function hash(s::SubString{String}, h::UInt)
139
    h += memhash_seed
×
140
    ccall(memhash, UInt, (Ptr{UInt8}, Csize_t, UInt32), s, sizeof(s), h % UInt32) + h
129,709✔
141
end
142

143
_isannotated(::SubString{T}) where {T} = _isannotated(T)
×
144

145
"""
146
    reverse(s::AbstractString) -> AbstractString
147

148
Reverses a string. Technically, this function reverses the codepoints in a string and its
149
main utility is for reversed-order string processing, especially for reversed
150
regular-expression searches. See also [`reverseind`](@ref) to convert indices in `s` to
151
indices in `reverse(s)` and vice-versa, and `graphemes` from module `Unicode` to
152
operate on user-visible "characters" (graphemes) rather than codepoints.
153
See also [`Iterators.reverse`](@ref) for
154
reverse-order iteration without making a copy. Custom string types must implement the
155
`reverse` function themselves and should typically return a string with the same type
156
and encoding. If they return a string with a different encoding, they must also override
157
`reverseind` for that string type to satisfy `s[reverseind(s,i)] == reverse(s)[i]`.
158

159
# Examples
160
```jldoctest
161
julia> reverse("JuliaLang")
162
"gnaLailuJ"
163
```
164

165
!!! note
166
    The examples below may be rendered differently on different systems.
167
    The comments indicate how they're supposed to be rendered
168

169
Combining characters can lead to surprising results:
170

171
```jldoctest
172
julia> reverse("ax̂e") # hat is above x in the input, above e in the output
173
"êxa"
174

175
julia> using Unicode
176

177
julia> join(reverse(collect(graphemes("ax̂e")))) # reverses graphemes; hat is above x in both in- and output
178
"ex̂a"
179
```
180
"""
181
function reverse(s::Union{String,SubString{String}})::String
×
182
    # Read characters forwards from `s` and write backwards to `out`
183
    out = _string_n(sizeof(s))
×
184
    offs = sizeof(s) + 1
×
185
    for c in s
×
186
        offs -= ncodeunits(c)
×
187
        __unsafe_string!(out, c, offs)
×
188
    end
×
189
    return out
×
190
end
191

UNCOV
192
string(a::String)            = String(a)
×
193
string(a::SubString{String}) = String(a)
14✔
194

195
function Symbol(s::SubString{String})
196
    return ccall(:jl_symbol_n, Ref{Symbol}, (Ptr{UInt8}, Int), s, sizeof(s))
×
197
end
198

199
@inline function __unsafe_string!(out, c::Char, offs::Integer) # out is a (new) String (or StringVector)
200
    x = bswap(reinterpret(UInt32, c))
18,709✔
201
    n = ncodeunits(c)
18,709✔
202
    GC.@preserve out begin
18,709✔
203
        unsafe_store!(pointer(out, offs), x % UInt8)
18,709✔
204
        n == 1 && return n
18,709✔
205
        x >>= 8
×
206
        unsafe_store!(pointer(out, offs+1), x % UInt8)
×
207
        n == 2 && return n
×
208
        x >>= 8
×
209
        unsafe_store!(pointer(out, offs+2), x % UInt8)
×
210
        n == 3 && return n
×
211
        x >>= 8
×
212
        unsafe_store!(pointer(out, offs+3), x % UInt8)
×
213
    end
214
    return n
×
215
end
216

217
@assume_effects :nothrow @inline function __unsafe_string!(out, s::String, offs::Integer)
218
    n = sizeof(s)
303✔
219
    GC.@preserve s out unsafe_copyto!(pointer(out, offs), pointer(s), n)
303✔
220
    return n
303✔
221
end
222

223
@inline function __unsafe_string!(out, s::SubString{String}, offs::Integer)
224
    n = sizeof(s)
17✔
225
    GC.@preserve s out unsafe_copyto!(pointer(out, offs), pointer(s), n)
17✔
226
    return n
17✔
227
end
228

229
@assume_effects :nothrow @inline function __unsafe_string!(out, s::Symbol, offs::Integer)
230
    n = sizeof(s)
×
231
    GC.@preserve s out unsafe_copyto!(pointer(out, offs), unsafe_convert(Ptr{UInt8},s), n)
×
232
    return n
×
233
end
234

235
# nothrow needed here because for v in a can't prove the indexing is inbounds.
236
@assume_effects :foldable :nothrow string(a::Union{Char, String, Symbol}...) = _string(a...)
254,405✔
237

238
string(a::Union{Char, String, SubString{String}, Symbol}...) = _string(a...)
257,808✔
239

240
function _string(a::Union{Char, String, SubString{String}, Symbol}...)
156✔
241
    n = 0
×
242
    for v in a
156✔
243
        # 4 types is too many for automatic Union-splitting, so we split manually
244
        # and allow one specializable call site per concrete type
245
        if v isa Char
34✔
246
            n += ncodeunits(v)
1✔
247
        elseif v isa String
34✔
248
            n += sizeof(v)
303✔
249
        elseif v isa SubString{String}
17✔
250
            n += sizeof(v)
17✔
251
        else
252
            n += sizeof(v::Symbol)
×
253
        end
254
    end
475✔
255
    out = _string_n(n)
156✔
256
    offs = 1
×
257
    for v in a
156✔
258
        if v isa Char
34✔
259
            offs += __unsafe_string!(out, v, offs)
1✔
260
        elseif v isa String || v isa SubString{String}
51✔
261
            offs += __unsafe_string!(out, v, offs)
320✔
262
        else
263
            offs += __unsafe_string!(out, v::Symbol, offs)
×
264
        end
265
    end
475✔
266
    return out
156✔
267
end
268

269
# don't assume effects for general integers since we cannot know their implementation
270
# not nothrow because r<0 throws
271
@assume_effects :foldable repeat(s::String, r::BitInteger) = @invoke repeat(s::String, r::Integer)
74✔
272

273
function repeat(s::Union{String, SubString{String}}, r::Integer)
7✔
274
    r < 0 && throw(ArgumentError("can't repeat a string $r times"))
7✔
275
    r = UInt(r)::UInt
7✔
276
    r == 0 && return ""
7✔
277
    r == 1 && return String(s)
7✔
278
    n = sizeof(s)
7✔
279
    out = _string_n(n*r)
7✔
280
    if n == 1 # common case: repeating a single-byte string
7✔
281
        @inbounds b = codeunit(s, 1)
7✔
282
        memset(unsafe_convert(Ptr{UInt8}, out), b, r)
7✔
283
    else
284
        for i = 0:r-1
×
285
            GC.@preserve s out unsafe_copyto!(pointer(out, i*n+1), pointer(s), n)
×
286
        end
×
287
    end
288
    return out
7✔
289
end
290

291
function filter(f, s::Union{String, SubString{String}})
×
292
    out = StringVector(sizeof(s))
×
293
    offset = 1
×
294
    for c in s
×
295
        if f(c)
×
296
            offset += __unsafe_string!(out, c, offset)
×
297
        end
298
    end
×
299
    resize!(out, offset-1)
×
300
    sizehint!(out, offset-1)
×
301
    return String(out)
×
302
end
303

304
getindex(s::AbstractString, r::AbstractUnitRange{<:Integer}) = SubString(s, r)
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc