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

JuliaLang / julia / #37433

pending completion
#37433

push

local

web-flow
Merge pull request #48513 from JuliaLang/jn/extend-once

ensure extension triggers are only run by the package that satified them

60 of 60 new or added lines in 1 file covered. (100.0%)

72324 of 82360 relevant lines covered (87.81%)

31376331.4 hits per line

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

91.95
/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
6,328,985✔
31
        i ≤ j || return new(s, 0, 0)
7,139,864✔
32
        @boundscheck begin
5,518,093✔
33
            checkbounds(s, i:j)
5,518,110✔
34
            @inbounds isvalid(s, i) || string_index_err(s, i)
5,518,074✔
35
            @inbounds isvalid(s, j) || string_index_err(s, j)
5,518,080✔
36
        end
37
        return new(s, i-1, nextind(s,j)-i)
5,518,060✔
38
    end
39
end
40

41
@propagate_inbounds SubString(s::T, i::Int, j::Int) where {T<:AbstractString} = SubString{T}(s, i, j)
6,329,030✔
42
@propagate_inbounds SubString(s::AbstractString, i::Integer, j::Integer=lastindex(s)) = SubString(s, Int(i), Int(j))
3,107,526✔
43
@propagate_inbounds SubString(s::AbstractString, r::AbstractUnitRange{<:Integer}) = SubString(s, first(r), last(r))
41,202✔
44

45
@propagate_inbounds function SubString(s::SubString, i::Int, j::Int)
389✔
46
    @boundscheck i ≤ j && checkbounds(s, i:j)
179,219✔
47
    SubString(s.string, s.offset+i, s.offset+j)
179,208✔
48
end
49

50
SubString(s::AbstractString) = SubString(s, 1, lastindex(s)::Int)
648✔
51
SubString{T}(s::T) where {T<:AbstractString} = SubString{T}(s, 1, lastindex(s)::Int)
×
52

53
@propagate_inbounds view(s::AbstractString, r::AbstractUnitRange{<:Integer}) = SubString(s, r)
454✔
54
@propagate_inbounds maybeview(s::AbstractString, r::AbstractUnitRange{<:Integer}) = view(s, r)
3✔
55
@propagate_inbounds maybeview(s::AbstractString, args...) = getindex(s, args...)
6✔
56

57
convert(::Type{SubString{S}}, s::AbstractString) where {S<:AbstractString} =
60✔
58
    SubString(convert(S, s))::SubString{S}
59
convert(::Type{T}, s::T) where {T<:SubString} = s
77✔
60

61
# Regex match allows only Union{String, SubString{String}} so define conversion to this type
62
convert(::Type{Union{String, SubString{String}}}, s::String) = s
×
63
convert(::Type{Union{String, SubString{String}}}, s::SubString{String}) = s
×
64
convert(::Type{Union{String, SubString{String}}}, s::AbstractString) = convert(String, s)::String
×
65

66
function String(s::SubString{String})
1,596✔
67
    parent = s.string
375,785✔
68
    copy = GC.@preserve parent unsafe_string(pointer(parent, s.offset+1), s.ncodeunits)
375,785✔
69
    return copy
375,785✔
70
end
71

72
ncodeunits(s::SubString) = s.ncodeunits
26,379,928✔
73
codeunit(s::SubString) = codeunit(s.string)::CodeunitType
×
74
length(s::SubString) = length(s.string, s.offset+1, s.offset+s.ncodeunits)
173,655✔
75

76
function codeunit(s::SubString, i::Integer)
3,025✔
77
    @boundscheck checkbounds(s, i)
1,352,539✔
78
    @inbounds return codeunit(s.string, s.offset + i)
1,352,539✔
79
end
80

81
function iterate(s::SubString, i::Integer=firstindex(s))
526,460✔
82
    i == ncodeunits(s)+1 && return nothing
7,230,240✔
83
    @boundscheck checkbounds(s, i)
3,800,891✔
84
    y = iterate(s.string, s.offset + i)
7,307,539✔
85
    y === nothing && return nothing
3,800,887✔
86
    c, i = y::Tuple{AbstractChar,Int}
3,800,887✔
87
    return c, i - s.offset
3,800,887✔
88
end
89

90
function getindex(s::SubString, i::Integer)
625,181✔
91
    @boundscheck checkbounds(s, i)
625,183✔
92
    @inbounds return getindex(s.string, s.offset + i)
625,179✔
93
end
94

95
function isvalid(s::SubString, i::Integer)
70,695✔
96
    ib = true
233✔
97
    @boundscheck ib = checkbounds(Bool, s, i)
25,009,328✔
98
    @inbounds return ib && isvalid(s.string, s.offset + i)::Bool
25,011,311✔
99
end
100

101
byte_string_classify(s::SubString{String}) =
2✔
102
    ccall(:u8_isvalid, Int32, (Ptr{UInt8}, Int), s, sizeof(s))
103

104
isvalid(::Type{String}, s::SubString{String}) = byte_string_classify(s) ≠ 0
2✔
105
isvalid(s::SubString{String}) = isvalid(String, s)
2✔
106

107
thisind(s::SubString{String}, i::Int) = _thisind_str(s, i)
688,399✔
108
nextind(s::SubString{String}, i::Int) = _nextind_str(s, i)
620,156✔
109

110
function ==(a::Union{String, SubString{String}}, b::Union{String, SubString{String}})
18,184✔
111
    s = sizeof(a)
8,015,216✔
112
    s == sizeof(b) && 0 == _memcmp(a, b, s)
15,759,125✔
113
end
114

115
function cmp(a::SubString{String}, b::SubString{String})
×
116
    na = sizeof(a)
×
117
    nb = sizeof(b)
×
118
    c = _memcmp(a, b, min(na, nb))
×
119
    return c < 0 ? -1 : c > 0 ? +1 : cmp(na, nb)
×
120
end
121

122
# don't make unnecessary copies when passing substrings to C functions
123
cconvert(::Type{Ptr{UInt8}}, s::SubString{String}) = s
×
124
cconvert(::Type{Ptr{Int8}}, s::SubString{String}) = s
2✔
125

126
function unsafe_convert(::Type{Ptr{R}}, s::SubString{String}) where R<:Union{Int8, UInt8}
183✔
127
    convert(Ptr{R}, pointer(s.string)) + s.offset
303,308✔
128
end
129

130
pointer(x::SubString{String}) = pointer(x.string) + x.offset
3,180,419✔
131
pointer(x::SubString{String}, i::Integer) = pointer(x.string) + x.offset + (i-1)
64✔
132

133
function hash(s::SubString{String}, h::UInt)
284✔
134
    h += memhash_seed
284✔
135
    ccall(memhash, UInt, (Ptr{UInt8}, Csize_t, UInt32), s, sizeof(s), h % UInt32) + h
373✔
136
end
137

138
"""
139
    reverse(s::AbstractString) -> AbstractString
140

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

152
# Examples
153
```jldoctest
154
julia> reverse("JuliaLang")
155
"gnaLailuJ"
156
```
157

158
!!! note
159
    The examples below may be rendered differently on different systems.
160
    The comments indicate how they're supposed to be rendered
161

162
Combining characters can lead to surprising results:
163

164
```jldoctest
165
julia> reverse("ax̂e") # hat is above x in the input, above e in the output
166
"êxa"
167

168
julia> using Unicode
169

170
julia> join(reverse(collect(graphemes("ax̂e")))) # reverses graphemes; hat is above x in both in- and output
171
"ex̂a"
172
```
173
"""
174
function reverse(s::Union{String,SubString{String}})::String
1,054✔
175
    # Read characters forwards from `s` and write backwards to `out`
176
    out = _string_n(sizeof(s))
1,054✔
177
    offs = sizeof(s) + 1
1,054✔
178
    for c in s
1,984✔
179
        offs -= ncodeunits(c)
42,481✔
180
        __unsafe_string!(out, c, offs)
40,015✔
181
    end
74,841✔
182
    return out
1,054✔
183
end
184

185
string(a::String)            = String(a)
46,852✔
186
string(a::SubString{String}) = String(a)
95✔
187

188
function Symbol(s::SubString{String})
114✔
189
    return ccall(:jl_symbol_n, Ref{Symbol}, (Ptr{UInt8}, Int), s, sizeof(s))
408✔
190
end
191

192
@inline function __unsafe_string!(out, c::Char, offs::Integer) # out is a (new) String (or StringVector)
27,345✔
193
    x = bswap(reinterpret(UInt32, c))
1,807,255✔
194
    n = ncodeunits(c)
1,922,714✔
195
    GC.@preserve out begin
1,807,277✔
196
        unsafe_store!(pointer(out, offs), x % UInt8)
1,807,277✔
197
        n == 1 && return n
1,807,255✔
198
        x >>= 8
58,240✔
199
        unsafe_store!(pointer(out, offs+1), x % UInt8)
58,240✔
200
        n == 2 && return n
58,240✔
201
        x >>= 8
54,566✔
202
        unsafe_store!(pointer(out, offs+2), x % UInt8)
54,566✔
203
        n == 3 && return n
54,566✔
204
        x >>= 8
2,653✔
205
        unsafe_store!(pointer(out, offs+3), x % UInt8)
2,653✔
206
    end
207
    return n
2,653✔
208
end
209

210
@inline function __unsafe_string!(out, s::Union{String, SubString{String}}, offs::Integer)
26,946✔
211
    n = sizeof(s)
12,960,170✔
212
    GC.@preserve s out unsafe_copyto!(pointer(out, offs), pointer(s), n)
12,960,170✔
213
    return n
12,960,170✔
214
end
215

216
@inline function __unsafe_string!(out, s::Symbol, offs::Integer)
×
217
    n = sizeof(s)
15,005✔
218
    GC.@preserve s out unsafe_copyto!(pointer(out, offs), unsafe_convert(Ptr{UInt8},s), n)
15,005✔
219
    return n
15,005✔
220
end
221

222
function string(a::Union{Char, String, SubString{String}, Symbol}...)
6,116,443✔
223
    n = 0
11,480✔
224
    for v in a
6,116,465✔
225
        # 4 types is too many for automatic Union-splitting, so we split manually
226
        # and allow one specializable call site per concrete type
227
        if v isa Char
216,331✔
228
            n += ncodeunits(v)
278,823✔
229
        elseif v isa String
664,828✔
230
            n += sizeof(v)
12,666,496✔
231
        elseif v isa SubString{String}
50,077✔
232
            n += sizeof(v)
293,674✔
233
        else
234
            n += sizeof(v::Symbol)
15,005✔
235
        end
236
    end
19,047,773✔
237
    out = _string_n(n)
6,116,465✔
238
    offs = 1
11,480✔
239
    for v in a
6,116,465✔
240
        if v isa Char
216,331✔
241
            offs += __unsafe_string!(out, v, offs)
226,872✔
242
        elseif v isa String || v isa SubString{String}
712,737✔
243
            offs += __unsafe_string!(out, v, offs)
13,217,702✔
244
        else
245
            offs += __unsafe_string!(out, v::Symbol, offs)
15,005✔
246
        end
247
    end
19,047,773✔
248
    return out
6,116,465✔
249
end
250

251
function repeat(s::Union{String, SubString{String}}, r::Integer)
1,000,258✔
252
    r < 0 && throw(ArgumentError("can't repeat a string $r times"))
1,000,258✔
253
    r == 0 && return ""
1,000,250✔
254
    r == 1 && return String(s)
876,708✔
255
    n = sizeof(s)
717,512✔
256
    out = _string_n(n*r)
717,512✔
257
    if n == 1 # common case: repeating a single-byte string
717,512✔
258
        @inbounds b = codeunit(s, 1)
676,034✔
259
        ccall(:memset, Ptr{Cvoid}, (Ptr{UInt8}, Cint, Csize_t), out, b, r)
676,034✔
260
    else
261
        for i = 0:r-1
82,956✔
262
            GC.@preserve s out unsafe_copyto!(pointer(out, i*n+1), pointer(s), n)
2,064,649✔
263
        end
2,064,649✔
264
    end
265
    return out
717,512✔
266
end
267

268
function filter(f, s::Union{String, SubString{String}})
42✔
269
    out = StringVector(sizeof(s))
42✔
270
    offset = 1
3✔
271
    for c in s
84✔
272
        if f(c)
844✔
273
            offset += __unsafe_string!(out, c, offset)
661✔
274
        end
275
    end
1,637✔
276
    resize!(out, offset-1)
84✔
277
    sizehint!(out, offset-1)
42✔
278
    return String(out)
42✔
279
end
280

281
getindex(s::AbstractString, r::AbstractUnitRange{<:Integer}) = SubString(s, r)
2,252✔
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