-
Notifications
You must be signed in to change notification settings - Fork 0
/
interface.jl
308 lines (248 loc) · 8.94 KB
/
interface.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
"""
NoData
Internal type for the `Metadata` package that indicates the absence of any metadata.
_DO NOT_ store metadata with the value `NoData()`.
!!! warning
This is not part of the public API and may change without notice.
"""
struct NoData end
Base.keys(::NoData) = ()
Base.values(::NoData) = ()
Base.haskey(::NoData, @nospecialize(k)) = false
Base.get(::NoData, @nospecialize(k), d) = d
Base.get(f::Union{Type,Function}, ::NoData, @nospecialize(k)) = f()
Base.iterate(::NoData) = nothing
Base.in(_, ::NoData) = false
const no_data = NoData()
Base.show(io::IO, ::NoData) = show(io, MIME"text/plain"(), no_data)
Base.show(io::IO, ::MIME"text/plain", ::NoData) = print(io, "no_data")
"""
MDNode(key, data)
Dedicated type for associating metadata with `key`. This is used to selectively reach
metadata using `MDNodedata.getmeta(x, key, d)`. `key` must be of a singleton type.
!!! warning "Experimental"
This is experimental and may change without warning
"""
struct MDNode{M,P,K}
parent::P
metadata::M
global _MDNode
_MDNode(@nospecialize(k), @nospecialize(p), @nospecialize(m)) = new{typeof(m),typeof(p),k}(p, m)
_MDNode(@nospecialize(k), @nospecialize(p)) = new{NoData,typeof(p),k}(p, no_data)
end
function MDNode(k::K, @nospecialize(p), @nospecialize(m::MDNode)) where {K}
@assert Base.issingletontype(K)
_MDNode(k, p, m)
end
function MDNode(k::K, @nospecialize(p), ::NoData) where {K}
@assert Base.issingletontype(K)
_MDNode(k, p, m)
end
function MDNode(k::K, @nospecialize(p)) where {K}
@assert Base.issingletontype(K)
_MDNode(k, p)
end
ArrayInterface.parent_type(@nospecialize T::Type{<:MDNode}) = T.parameters[2]
Base.parent(@nospecialize x::MDNode) = getfield(x, :parent)
"""
Metadata.rmkey(m::MDNode) -> parent(m)
Metadata.rmkey(m) -> m
Returns the the metadata key associated bound to `m`, if `m` is `MDNode`. This is only
intended for internal use.
!!! warning
This is experimental and may change without warning
"""
rmkey(@nospecialize x::MDNode) = parent(x)
rmkey(@nospecialize x) = x
"""
Metadata.metakey(m)
Returns the key associated withe the metadata `m`. The only way to attach a key to
metadata is through `MDNode(key, m)`.
!!! warning
This is experimental and may change without warning
"""
metakey(@nospecialize x::MDNode) = metakey(typeof(x))
metakey(@nospecialize T::Type{<:MDNode}) = T.parameters[3]
Base.show(io::IO, ::MIME"text/plain", @nospecialize(m::MDNode)) = printmeta(io, m, "")
"""
MDList(first::MDNode, tail::Union{MDList,NoData})
Iterable list of metadata.
!!! warning "Experimental"
This is experimental and may change without warning
"""
struct MDList{F,T}
first::F
tail::T
MDList(@nospecialize(f::MDNode), @nospecialize(t::MDList)) = new{typeof(f),typeof(t)}(f, t)
MDList(@nospecialize(f::MDNode), t::NoData) = new{typeof(f),NoData}(f, t)
end
const MDListEnd{H} = MDList{H,NoData}
Base.first(@nospecialize mdl::MDList) = getfield(mdl, :first)
Base.tail(@nospecialize mdl::MDList) = getfield(mdl, :tail)
Base.iterate(@nospecialize(mdl::MDList)) = first(mdl), tail(mdl)
@inline function Base.iterate(@nospecialize(mdl::MDList), @nospecialize(state))
if state === no_data
return nothing
else
return first(state), tail(state)
end
end
function Base.length(@nospecialize mdl::MDList)
t = tail(mdl)
if t === no_data
return 1
else
return 1 + length(mdl)
end
end
Base.show(io::IO, ::MIME"text/plain", @nospecialize(m::MDList)) = printmeta(io, m, "")
"""
metadata(x)
Returns metadata immediately bound to `x`. If no metadata is bound to `x` then
`Metadata.no_data` is returned.
"""
metadata(x) = no_data
metadata(@nospecialize x::MDNode) = getfield(x, :metadata)
"""
metadata_type(::Type{T})
Returns the type of the metadata associated with `T`.
"""
metadata_type(x) = metadata_type(typeof(x))
metadata_type(T::DataType) = NoData
metadata_type(@nospecialize m::MDNode) = typeof(m).parameters[1]
metadata_type(@nospecialize T::Type{<:MDNode}) = T.parameters[1]
# This summarizes all nested metadata wrappers using only the singleton types.
# The benefits are:
#
# - The return value is agnostic to the type of any parent data (e.g.,
# `parent(::MetaStruct)`, `parent(::MDNode)`, etc.), thus avoiding extra code gen.
# - Since everything is a singleton type it's easy for the compiler to completely inline
# even deeply nested types.
#
# Once we have this type we can use it to create a map of all the metadata nodes in the type
# domain without losing information or trying to specialize anymore.
@inline layout(@nospecialize x::MDNode) = _MDNode(metakey(x), layout(parent(x)))
@inline function layout(@nospecialize x)
m = metadata(x)
if m !== no_data
return _MetaStruct(layout(parent(x)), layout(m))
elseif Base.issingletontype(typeof(x))
return x
else
return no_data
end
end
"""
has_metadata(x)::Bool
Returns `true` if `x` has metadata.
"""
@inline has_metadata(x) = !(metadata_type(x) <: NoData)
"""
stripmeta(x) -> (data, metadata)
Returns the the data and metadata immediately bound to `x`.
"""
@inline function stripmeta(@nospecialize x)
if metadata_type(x) <: NoData
return (x, no_data)
else
return (parent(x), metadata(x))
end
end
"""
getmeta(x, key, default)
Return the metadata associated with `key`, or return `default` if `key` is not found.
"""
@inline getmeta(x, k, default) = _getmeta(metadata(x), k, default)
_getmeta(m, k, default) = get(m, k, default)
@inline _getmeta(m::AbstractDict{String}, k::Symbol, default) = get(m, String(k), default)
@inline _getmeta(m::AbstractDict{Symbol}, k::String, default) = get(m, Symbol(k), default)
@inline _getmeta(m::NamedTuple, k::String, default) = get(m, Symbol(k), default)
"""
getmeta(f::Function, x, key)
Return the metadata associated with `key`, or return `f(x)` if `key` is not found. Note that
this behavior differs from `Base.get(::Function, x, keys)` in that `getmeta` passes `x` to
`f` as an argument (as opposed to `f()`).
"""
@inline function getmeta(f::Union{Function,Type}, x, k)
m = getmeta(metadata(x), k, no_data)
if m === no_data
return f(x)
else
return m
end
end
"""
has_metadata(x, k)::Bool
Returns `true` if metadata associated with `x` has the key `k`.
"""
has_metadata(x, k) = haskey(metadata(x), k)
"""
attach_metadata(x, m)
Generic method for attaching metadata `m` to data `x`. This method acts as an intermediate
step where compatability between `x` and `m` is checked using `checkmeta`.
`unsafe_attach_metadata` is subsequently used to quickly bind the two without and further
checks.
# Extended help
In general, it is not advised to define new `attach_metadata` methods. Instead,
unique types for binding `x` to metadata should define a new `unsafe_attach_metadata`
method. For example, attaching metadata to `AbstractArray` types by defining a unique method
for `unsafe_attach_metadata(x::AbstractArray, m)`.
See also: [`unsafe_attach_metadata`](@ref), [`checkmeta`](@ref)
"""
function attach_metadata(x, m)
checkmeta(x, m)
unsafe_attach_metadata(x, m)
end
attach_metadata(x, ::NoData) = x
attach_metadata(m) = Base.Fix2(attach_metadata, m)
"""
checkmeta([Type{Bool}], x, m)
Checks if the metadata `m` is compatible with `x`. If `Bool` is not included then an error
is throw on failure.
!!! warning
This is experimentaland may change without warning
"""
checkmeta(x, m) = checkmeta(Bool, x, m) || throw(ArgumentError("data $x and metadata $m are incompatible."))
checkmeta(::Type{Bool}, x, m) = true
"""
unsafe_attach_metadata(x, m)
Attaches metadata `m` to `x` without checking for compatability. New types for wrapping
binding metadata to `x` should usually define a unique `unsafe_attach_metadata` method.
See also [`attach_metadata`](@ref)
"""
unsafe_attach_metadata(x, m) = _MetaStruct(x, m)
unsafe_attach_metadata(@nospecialize(x::MetaStruct), ::NoData) = _MetaStruct(x, m)
unsafe_attach_metadata(@nospecialize(x::MetaStruct), @nospecialize(m)) = _MetaStruct(x, m)
"""
properties(x)
Returns properties associated with bound to `x`.
"""
properties(@nospecialize(x)) = metadata(x)
# printmeta
printmeta(io::IO, m, prefix::String) = print(io, m)
@inline function printmeta(io::IO, @nospecialize(mdl::MDList), prefix::String)
print(io, "MDList")
N = length(mdl)
i = 1
@inbounds for m_i in mdl
child_prefix = prefix
print(io, prefix)
if i === nm
print(io, "└")
child_prefix *= " " ^ (Base.textwidth("│") + Base.textwidth("─") + 1)
else
print(io, "├")
child_prefix *= "│" * " " ^ (Base.textwidth("─") + 1)
end
print(io, "─", ' ')
printmeta(io, m_i, child_prefix)
i += 1
end
end
function printmeta(io::IO, @nospecialize(m::MDNode), prefix::String)
print(io, metakey(mk), " => ", parent(m))
if !(metadata_type(m) <: NoData)
println(io)
printmeta(io, metadata(m), prefix)
end
end