Line | Exclusive | Inclusive | Code |
---|---|---|---|
1 | # This file is a part of Julia. License is MIT: https://julialang.org/license | ||
2 | |||
3 | # name and module reflection | ||
4 | |||
5 | """ | ||
6 | nameof(m::Module) -> Symbol | ||
7 | |||
8 | Get the name of a `Module` as a `Symbol`. | ||
9 | |||
10 | # Examples | ||
11 | ```jldoctest | ||
12 | julia> nameof(Base.Broadcast) | ||
13 | :Broadcast | ||
14 | ``` | ||
15 | """ | ||
16 | nameof(m::Module) = ccall(:jl_module_name, Ref{Symbol}, (Any,), m) | ||
17 | |||
18 | """ | ||
19 | parentmodule(m::Module) -> Module | ||
20 | |||
21 | Get a module's enclosing `Module`. `Main` is its own parent. | ||
22 | |||
23 | # Examples | ||
24 | ```jldoctest | ||
25 | julia> parentmodule(Main) | ||
26 | Main | ||
27 | |||
28 | julia> parentmodule(Base.Broadcast) | ||
29 | Base | ||
30 | ``` | ||
31 | """ | ||
32 | parentmodule(m::Module) = ccall(:jl_module_parent, Ref{Module}, (Any,), m) | ||
33 | |||
34 | """ | ||
35 | moduleroot(m::Module) -> Module | ||
36 | |||
37 | Find the root module of a given module. This is the first module in the chain of | ||
38 | parent modules of `m` which is either a registered root module or which is its | ||
39 | own parent module. | ||
40 | """ | ||
41 | function moduleroot(m::Module) | ||
42 | while true | ||
43 | is_root_module(m) && return m | ||
44 | p = parentmodule(m) | ||
45 | p == m && return m | ||
46 | m = p | ||
47 | end | ||
48 | end | ||
49 | |||
50 | """ | ||
51 | @__MODULE__ -> Module | ||
52 | |||
53 | Get the `Module` of the toplevel eval, | ||
54 | which is the `Module` code is currently being read from. | ||
55 | """ | ||
56 | macro __MODULE__() | ||
57 | return __module__ | ||
58 | end | ||
59 | |||
60 | """ | ||
61 | fullname(m::Module) | ||
62 | |||
63 | Get the fully-qualified name of a module as a tuple of symbols. For example, | ||
64 | |||
65 | # Examples | ||
66 | ```jldoctest | ||
67 | julia> fullname(Base.Iterators) | ||
68 | (:Base, :Iterators) | ||
69 | |||
70 | julia> fullname(Main) | ||
71 | (:Main,) | ||
72 | ``` | ||
73 | """ | ||
74 | function fullname(m::Module) | ||
75 | mn = nameof(m) | ||
76 | if m === Main || m === Base || m === Core | ||
77 | return (mn,) | ||
78 | end | ||
79 | mp = parentmodule(m) | ||
80 | if mp === m | ||
81 | return (mn,) | ||
82 | end | ||
83 | return (fullname(mp)..., mn) | ||
84 | end | ||
85 | |||
86 | """ | ||
87 | names(x::Module; all::Bool = false, imported::Bool = false) | ||
88 | |||
89 | Get an array of the names exported by a `Module`, excluding deprecated names. | ||
90 | If `all` is true, then the list also includes non-exported names defined in the module, | ||
91 | deprecated names, and compiler-generated names. | ||
92 | If `imported` is true, then names explicitly imported from other modules | ||
93 | are also included. | ||
94 | |||
95 | As a special case, all names defined in `Main` are considered \"exported\", | ||
96 | since it is not idiomatic to explicitly export names from `Main`. | ||
97 | """ | ||
98 | names(m::Module; all::Bool = false, imported::Bool = false) = | ||
99 | sort!(ccall(:jl_module_names, Array{Symbol,1}, (Any, Cint, Cint), m, all, imported)) | ||
100 | |||
101 | isexported(m::Module, s::Symbol) = ccall(:jl_module_exports_p, Cint, (Any, Any), m, s) != 0 | ||
102 | isdeprecated(m::Module, s::Symbol) = ccall(:jl_is_binding_deprecated, Cint, (Any, Any), m, s) != 0 | ||
103 | isbindingresolved(m::Module, var::Symbol) = ccall(:jl_binding_resolved_p, Cint, (Any, Any), m, var) != 0 | ||
104 | |||
105 | function binding_module(m::Module, s::Symbol) | ||
106 | p = ccall(:jl_get_module_of_binding, Ptr{Cvoid}, (Any, Any), m, s) | ||
107 | p == C_NULL && return m | ||
108 | return unsafe_pointer_to_objref(p)::Module | ||
109 | end | ||
110 | |||
111 | function resolve(g::GlobalRef; force::Bool=false) | ||
112 | if force || isbindingresolved(g.mod, g.name) | ||
113 | return GlobalRef(binding_module(g.mod, g.name), g.name) | ||
114 | end | ||
115 | return g | ||
116 | end | ||
117 | |||
118 | const NamedTuple_typename = NamedTuple.body.body.name | ||
119 | |||
120 | function _fieldnames(@nospecialize t) | ||
121 | if t.name === NamedTuple_typename | ||
122 | if t.parameters[1] isa Tuple | ||
123 | return t.parameters[1] | ||
124 | else | ||
125 | throw(ArgumentError("type does not have definite field names")) | ||
126 | end | ||
127 | end | ||
128 | isdefined(t, :names) ? t.names : t.name.names | ||
129 | end | ||
130 | |||
131 | """ | ||
132 | fieldname(x::DataType, i::Integer) | ||
133 | |||
134 | Get the name of field `i` of a `DataType`. | ||
135 | |||
136 | # Examples | ||
137 | ```jldoctest | ||
138 | julia> fieldname(Rational, 1) | ||
139 | :num | ||
140 | |||
141 | julia> fieldname(Rational, 2) | ||
142 | :den | ||
143 | ``` | ||
144 | """ | ||
145 | function fieldname(t::DataType, i::Integer) | ||
146 | if t.abstract | ||
147 | throw(ArgumentError("type does not have definite field names")) | ||
148 | end | ||
149 | names = _fieldnames(t) | ||
150 | n_fields = length(names) | ||
151 | field_label = n_fields == 1 ? "field" : "fields" | ||
152 | i > n_fields && throw(ArgumentError("Cannot access field $i since type $t only has $n_fields $field_label.")) | ||
153 | i < 1 && throw(ArgumentError("Field numbers must be positive integers. $i is invalid.")) | ||
154 | return names[i]::Symbol | ||
155 | end | ||
156 | |||
157 | fieldname(t::UnionAll, i::Integer) = fieldname(unwrap_unionall(t), i) | ||
158 | fieldname(t::Type{<:Tuple}, i::Integer) = | ||
159 | i < 1 || i > fieldcount(t) ? throw(BoundsError(t, i)) : Int(i) | ||
160 | |||
161 | """ | ||
162 | fieldnames(x::DataType) | ||
163 | |||
164 | Get a tuple with the names of the fields of a `DataType`. | ||
165 | |||
166 | # Examples | ||
167 | ```jldoctest | ||
168 | julia> fieldnames(Rational) | ||
169 | (:num, :den) | ||
170 | ``` | ||
171 | """ | ||
172 | fieldnames(t::DataType) = (fieldcount(t); # error check to make sure type is specific enough | ||
173 | (_fieldnames(t)...,)) | ||
174 | fieldnames(t::UnionAll) = fieldnames(unwrap_unionall(t)) | ||
175 | fieldnames(::Core.TypeofBottom) = | ||
176 | throw(ArgumentError("The empty type does not have field names since it does not have instances.")) | ||
177 | fieldnames(t::Type{<:Tuple}) = ntuple(identity, fieldcount(t)) | ||
178 | |||
179 | """ | ||
180 | nameof(t::DataType) -> Symbol | ||
181 | |||
182 | Get the name of a (potentially `UnionAll`-wrapped) `DataType` (without its parent module) | ||
183 | as a symbol. | ||
184 | |||
185 | # Examples | ||
186 | ```jldoctest | ||
187 | julia> module Foo | ||
188 | struct S{T} | ||
189 | end | ||
190 | end | ||
191 | Foo | ||
192 | |||
193 | julia> nameof(Foo.S{T} where T) | ||
194 | :S | ||
195 | ``` | ||
196 | """ | ||
197 | nameof(t::DataType) = t.name.name | ||
198 | nameof(t::UnionAll) = nameof(unwrap_unionall(t)) | ||
199 | |||
200 | """ | ||
201 | parentmodule(t::DataType) -> Module | ||
202 | |||
203 | Determine the module containing the definition of a (potentially `UnionAll`-wrapped) `DataType`. | ||
204 | |||
205 | # Examples | ||
206 | ```jldoctest | ||
207 | julia> module Foo | ||
208 | struct Int end | ||
209 | end | ||
210 | Foo | ||
211 | |||
212 | julia> parentmodule(Int) | ||
213 | Core | ||
214 | |||
215 | julia> parentmodule(Foo.Int) | ||
216 | Foo | ||
217 | ``` | ||
218 | """ | ||
219 | parentmodule(t::DataType) = t.name.module | ||
220 | parentmodule(t::UnionAll) = parentmodule(unwrap_unionall(t)) | ||
221 | |||
222 | """ | ||
223 | isconst(m::Module, s::Symbol) -> Bool | ||
224 | |||
225 | Determine whether a global is declared `const` in a given `Module`. | ||
226 | """ | ||
227 | isconst(m::Module, s::Symbol) = | ||
228 | ccall(:jl_is_const, Cint, (Any, Any), m, s) != 0 | ||
229 | |||
230 | """ | ||
231 | @isdefined s -> Bool | ||
232 | |||
233 | Tests whether variable `s` is defined in the current scope. | ||
234 | |||
235 | # Examples | ||
236 | ```jldoctest | ||
237 | julia> function f() | ||
238 | println(@isdefined x) | ||
239 | x = 3 | ||
240 | println(@isdefined x) | ||
241 | end | ||
242 | f (generic function with 1 method) | ||
243 | |||
244 | julia> f() | ||
245 | false | ||
246 | true | ||
247 | ``` | ||
248 | """ | ||
249 | macro isdefined(s::Symbol) | ||
250 | return Expr(:isdefined, esc(s)) | ||
251 | end | ||
252 | |||
253 | """ | ||
254 | objectid(x) | ||
255 | |||
256 | Get a hash value for `x` based on object identity. `objectid(x)==objectid(y)` if `x === y`. | ||
257 | """ | ||
258 | objectid(@nospecialize(x)) = ccall(:jl_object_id, UInt, (Any,), x) | ||
259 | |||
260 | # concrete datatype predicates | ||
261 | |||
262 | struct DataTypeLayout | ||
263 | nfields::UInt32 | ||
264 | alignment::UInt32 | ||
265 | # alignment : 28; | ||
266 | # haspadding : 1; | ||
267 | # pointerfree : 1; | ||
268 | # fielddesc_type : 2; | ||
269 | end | ||
270 | |||
271 | """ | ||
272 | Base.datatype_alignment(dt::DataType) -> Int | ||
273 | |||
274 | Memory allocation minimum alignment for instances of this type. | ||
275 | Can be called on any `isconcretetype`. | ||
276 | """ | ||
277 | function datatype_alignment(dt::DataType) | ||
278 | @_pure_meta | ||
279 | dt.layout == C_NULL && throw(UndefRefError()) | ||
280 | alignment = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).alignment | ||
281 | return Int(alignment & 0x1FF) | ||
282 | end | ||
283 | |||
284 | """ | ||
285 | Base.datatype_haspadding(dt::DataType) -> Bool | ||
286 | |||
287 | Return whether the fields of instances of this type are packed in memory, | ||
288 | with no intervening padding bytes. | ||
289 | Can be called on any `isconcretetype`. | ||
290 | """ | ||
291 | function datatype_haspadding(dt::DataType) | ||
292 | @_pure_meta | ||
293 | dt.layout == C_NULL && throw(UndefRefError()) | ||
294 | alignment = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).alignment | ||
295 | return (alignment >> 9) & 1 == 1 | ||
296 | end | ||
297 | |||
298 | """ | ||
299 | Base.datatype_pointerfree(dt::DataType) -> Bool | ||
300 | |||
301 | Return whether instances of this type can contain references to gc-managed memory. | ||
302 | Can be called on any `isconcretetype`. | ||
303 | """ | ||
304 | function datatype_pointerfree(dt::DataType) | ||
305 | @_pure_meta | ||
306 | dt.layout == C_NULL && throw(UndefRefError()) | ||
307 | alignment = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).alignment | ||
308 | return (alignment >> 10) & 0xFFFFF == 0 | ||
309 | end | ||
310 | |||
311 | """ | ||
312 | Base.datatype_fielddesc_type(dt::DataType) -> Int | ||
313 | |||
314 | Return the size in bytes of each field-description entry in the layout array, | ||
315 | located at `(dt.layout + sizeof(DataTypeLayout))`. | ||
316 | Can be called on any `isconcretetype`. | ||
317 | |||
318 | See also [`Base.fieldoffset`](@ref). | ||
319 | """ | ||
320 | function datatype_fielddesc_type(dt::DataType) | ||
321 | @_pure_meta | ||
322 | dt.layout == C_NULL && throw(UndefRefError()) | ||
323 | alignment = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).alignment | ||
324 | return (alignment >> 30) & 3 | ||
325 | end | ||
326 | |||
327 | """ | ||
328 | isimmutable(v) -> Bool | ||
329 | |||
330 | Return `true` iff value `v` is immutable. See [Mutable Composite Types](@ref) | ||
331 | for a discussion of immutability. Note that this function works on values, so if you give it | ||
332 | a type, it will tell you that a value of `DataType` is mutable. | ||
333 | |||
334 | # Examples | ||
335 | ```jldoctest | ||
336 | julia> isimmutable(1) | ||
337 | true | ||
338 | |||
339 | julia> isimmutable([1,2]) | ||
340 | false | ||
341 | ``` | ||
342 | """ | ||
343 | isimmutable(@nospecialize(x)) = (@_pure_meta; !typeof(x).mutable) | ||
344 | |||
345 | """ | ||
346 | Base.isstructtype(T) -> Bool | ||
347 | |||
348 | Determine whether type `T` was declared as a struct type | ||
349 | (i.e. using the `struct` or `mutable struct` keyword). | ||
350 | """ | ||
351 | function isstructtype(@nospecialize(t::Type)) | ||
352 | @_pure_meta | ||
353 | t = unwrap_unionall(t) | ||
354 | # TODO: what to do for `Union`? | ||
355 | isa(t, DataType) || return false | ||
356 | return length(t.types) != 0 || (t.size == 0 && !t.abstract) | ||
357 | end | ||
358 | |||
359 | """ | ||
360 | Base.isprimitivetype(T) -> Bool | ||
361 | |||
362 | Determine whether type `T` was declared as a primitive type | ||
363 | (i.e. using the `primitive` keyword). | ||
364 | """ | ||
365 | function isprimitivetype(@nospecialize(t::Type)) | ||
366 | @_pure_meta | ||
367 | t = unwrap_unionall(t) | ||
368 | # TODO: what to do for `Union`? | ||
369 | isa(t, DataType) || return false | ||
370 | return length(t.types) == 0 && t.size != 0 && !t.abstract | ||
371 | end | ||
372 | |||
373 | """ | ||
374 | isbitstype(T) | ||
375 | |||
376 | Return `true` if type `T` is a "plain data" type, | ||
377 | meaning it is immutable and contains no references to other values, | ||
378 | only `primitive` types and other `isbitstype` types. | ||
379 | Typical examples are numeric types such as [`UInt8`](@ref), | ||
380 | [`Float64`](@ref), and [`Complex{Float64}`](@ref). | ||
381 | This category of types is significant since they are valid as type parameters, | ||
382 | may not track [`isdefined`](@ref) / [`isassigned`](@ref) status, | ||
383 | and have a defined layout that is compatible with C. | ||
384 | |||
385 | # Examples | ||
386 | ```jldoctest | ||
387 | julia> isbitstype(Complex{Float64}) | ||
388 | true | ||
389 | |||
390 | julia> isbitstype(Complex) | ||
391 | false | ||
392 | ``` | ||
393 | """ | ||
394 | isbitstype(@nospecialize(t::Type)) = (@_pure_meta; isa(t, DataType) && t.isbitstype) | ||
395 | |||
396 | """ | ||
397 | isbits(x) | ||
398 | |||
399 | Return `true` if `x` is an instance of an `isbitstype` type. | ||
400 | """ | ||
401 | isbits(@nospecialize x) = (@_pure_meta; typeof(x).isbitstype) | ||
402 | |||
403 | """ | ||
404 | isdispatchtuple(T) | ||
405 | |||
406 | Determine whether type `T` is a tuple "leaf type", | ||
407 | meaning it could appear as a type signature in dispatch | ||
408 | and has no subtypes (or supertypes) which could appear in a call. | ||
409 | """ | ||
410 | isdispatchtuple(@nospecialize(t)) = (@_pure_meta; isa(t, DataType) && t.isdispatchtuple) | ||
411 | |||
412 | iskindtype(@nospecialize t) = (t === DataType || t === UnionAll || t === Union || t === typeof(Bottom)) | ||
413 | isconcretedispatch(@nospecialize t) = isconcretetype(t) && !iskindtype(t) | ||
414 | has_free_typevars(@nospecialize(t)) = ccall(:jl_has_free_typevars, Cint, (Any,), t) != 0 | ||
415 | |||
416 | # equivalent to isa(v, Type) && isdispatchtuple(Tuple{v}) || v === Union{} | ||
417 | # and is thus perhaps most similar to the old (pre-1.0) `isleaftype` query | ||
418 | const _TYPE_NAME = Type.body.name | ||
419 | function isdispatchelem(@nospecialize v) | ||
420 | return (v === Bottom) || (v === typeof(Bottom)) || isconcretedispatch(v) || | ||
421 | (isa(v, DataType) && v.name === _TYPE_NAME && !has_free_typevars(v)) # isType(v) | ||
422 | end | ||
423 | |||
424 | """ | ||
425 | isconcretetype(T) | ||
426 | |||
427 | Determine whether type `T` is a concrete type, meaning it could have direct instances | ||
428 | (values `x` such that `typeof(x) === T`). | ||
429 | |||
430 | # Examples | ||
431 | ```jldoctest | ||
432 | julia> isconcretetype(Complex) | ||
433 | false | ||
434 | |||
435 | julia> isconcretetype(Complex{Float32}) | ||
436 | true | ||
437 | |||
438 | julia> isconcretetype(Vector{Complex}) | ||
439 | true | ||
440 | |||
441 | julia> isconcretetype(Vector{Complex{Float32}}) | ||
442 | true | ||
443 | |||
444 | julia> isconcretetype(Union{}) | ||
445 | false | ||
446 | |||
447 | julia> isconcretetype(Union{Int,String}) | ||
448 | false | ||
449 | ``` | ||
450 | """ | ||
451 | isconcretetype(@nospecialize(t)) = (@_pure_meta; isa(t, DataType) && t.isconcretetype) | ||
452 | |||
453 | """ | ||
454 | Base.isabstracttype(T) | ||
455 | |||
456 | Determine whether type `T` was declared as an abstract type | ||
457 | (i.e. using the `abstract` keyword). | ||
458 | |||
459 | # Examples | ||
460 | ```jldoctest | ||
461 | julia> Base.isabstracttype(AbstractArray) | ||
462 | true | ||
463 | |||
464 | julia> Base.isabstracttype(Vector) | ||
465 | false | ||
466 | ``` | ||
467 | """ | ||
468 | function isabstracttype(@nospecialize(t)) | ||
469 | @_pure_meta | ||
470 | t = unwrap_unionall(t) | ||
471 | # TODO: what to do for `Union`? | ||
472 | return isa(t, DataType) && t.abstract | ||
473 | end | ||
474 | |||
475 | """ | ||
476 | Base.parameter_upper_bound(t::UnionAll, idx) | ||
477 | |||
478 | Determine the upper bound of a type parameter in the underlying datatype. | ||
479 | This method should generally not be relied upon: | ||
480 | code instead should usually use static parameters in dispatch to extract these values. | ||
481 | |||
482 | # Examples | ||
483 | ```jldoctest | ||
484 | julia> struct Foo{T<:AbstractFloat, N} | ||
485 | x::Tuple{T, N} | ||
486 | end | ||
487 | |||
488 | julia> Base.parameter_upper_bound(Foo, 1) | ||
489 | AbstractFloat | ||
490 | |||
491 | julia> Base.parameter_upper_bound(Foo, 2) | ||
492 | Any | ||
493 | ``` | ||
494 | """ | ||
495 | function parameter_upper_bound(t::UnionAll, idx) | ||
496 | @_pure_meta | ||
497 | return rewrap_unionall((unwrap_unionall(t)::DataType).parameters[idx], t) | ||
498 | end | ||
499 | |||
500 | """ | ||
501 | typeintersect(T, S) | ||
502 | |||
503 | Compute a type that contains the intersection of `T` and `S`. Usually this will be the | ||
504 | smallest such type or one close to it. | ||
505 | """ | ||
506 | typeintersect(@nospecialize(a),@nospecialize(b)) = (@_pure_meta; ccall(:jl_type_intersection, Any, (Any,Any), a, b)) | ||
507 | |||
508 | """ | ||
509 | fieldoffset(type, i) | ||
510 | |||
511 | The byte offset of field `i` of a type relative to the data start. For example, we could | ||
512 | use it in the following manner to summarize information about a struct: | ||
513 | |||
514 | ```jldoctest | ||
515 | julia> structinfo(T) = [(fieldoffset(T,i), fieldname(T,i), fieldtype(T,i)) for i = 1:fieldcount(T)]; | ||
516 | |||
517 | julia> structinfo(Base.Filesystem.StatStruct) | ||
518 | 12-element Array{Tuple{UInt64,Symbol,DataType},1}: | ||
519 | (0x0000000000000000, :device, UInt64) | ||
520 | (0x0000000000000008, :inode, UInt64) | ||
521 | (0x0000000000000010, :mode, UInt64) | ||
522 | (0x0000000000000018, :nlink, Int64) | ||
523 | (0x0000000000000020, :uid, UInt64) | ||
524 | (0x0000000000000028, :gid, UInt64) | ||
525 | (0x0000000000000030, :rdev, UInt64) | ||
526 | (0x0000000000000038, :size, Int64) | ||
527 | (0x0000000000000040, :blksize, Int64) | ||
528 | (0x0000000000000048, :blocks, Int64) | ||
529 | (0x0000000000000050, :mtime, Float64) | ||
530 | (0x0000000000000058, :ctime, Float64) | ||
531 | ``` | ||
532 | """ | ||
533 | fieldoffset(x::DataType, idx::Integer) = (@_pure_meta; ccall(:jl_get_field_offset, Csize_t, (Any, Cint), x, idx)) | ||
534 | |||
535 | """ | ||
536 | fieldtype(T, name::Symbol | index::Int) | ||
537 | |||
538 | Determine the declared type of a field (specified by name or index) in a composite DataType `T`. | ||
539 | |||
540 | # Examples | ||
541 | ```jldoctest | ||
542 | julia> struct Foo | ||
543 | x::Int64 | ||
544 | y::String | ||
545 | end | ||
546 | |||
547 | julia> fieldtype(Foo, :x) | ||
548 | Int64 | ||
549 | |||
550 | julia> fieldtype(Foo, 2) | ||
551 | String | ||
552 | ``` | ||
553 | """ | ||
554 | fieldtype | ||
555 | |||
556 | """ | ||
557 | fieldindex(T, name::Symbol, err:Bool=true) | ||
558 | |||
559 | Get the index of a named field, throwing an error if the field does not exist (when err==true) | ||
560 | or returning 0 (when err==false). | ||
561 | |||
562 | # Examples | ||
563 | ```jldoctest | ||
564 | julia> struct Foo | ||
565 | x::Int64 | ||
566 | y::String | ||
567 | end | ||
568 | |||
569 | julia> Base.fieldindex(Foo, :z) | ||
570 | ERROR: type Foo has no field z | ||
571 | Stacktrace: | ||
572 | [...] | ||
573 | |||
574 | julia> Base.fieldindex(Foo, :z, false) | ||
575 | 0 | ||
576 | ``` | ||
577 | """ | ||
578 | function fieldindex(T::DataType, name::Symbol, err::Bool=true) | ||
579 | return Int(ccall(:jl_field_index, Cint, (Any, Any, Cint), T, name, err)+1) | ||
580 | end | ||
581 | |||
582 | argument_datatype(@nospecialize t) = ccall(:jl_argument_datatype, Any, (Any,), t) | ||
583 | function argument_mt(@nospecialize t) | ||
584 | dt = argument_datatype(t) | ||
585 | (dt === nothing || !isdefined(dt.name, :mt)) && return nothing | ||
586 | dt.name.mt | ||
587 | end | ||
588 | |||
589 | """ | ||
590 | fieldcount(t::Type) | ||
591 | |||
592 | Get the number of fields that an instance of the given type would have. | ||
593 | An error is thrown if the type is too abstract to determine this. | ||
594 | """ | ||
595 | function fieldcount(@nospecialize t) | ||
596 | if t isa UnionAll || t isa Union | ||
597 | t = argument_datatype(t) | ||
598 | if t === nothing | ||
599 | throw(ArgumentError("type does not have a definite number of fields")) | ||
600 | end | ||
601 | t = t::DataType | ||
602 | elseif t == Union{} | ||
603 | throw(ArgumentError("The empty type does not have a well-defined number of fields since it does not have instances.")) | ||
604 | end | ||
605 | if !(t isa DataType) | ||
606 | throw(TypeError(:fieldcount, "", Type, t)) | ||
607 | end | ||
608 | if t.name === NamedTuple_typename | ||
609 | names, types = t.parameters | ||
610 | if names isa Tuple | ||
611 | return length(names) | ||
612 | end | ||
613 | if types isa DataType && types <: Tuple | ||
614 | return fieldcount(types) | ||
615 | end | ||
616 | abstr = true | ||
617 | else | ||
618 | abstr = t.abstract || (t.name === Tuple.name && isvatuple(t)) | ||
619 | end | ||
620 | if abstr | ||
621 | throw(ArgumentError("type does not have a definite number of fields")) | ||
622 | end | ||
623 | return length(t.types) | ||
624 | end | ||
625 | |||
626 | # return all instances, for types that can be enumerated | ||
627 | |||
628 | """ | ||
629 | instances(T::Type) | ||
630 | |||
631 | Return a collection of all instances of the given type, if applicable. Mostly used for | ||
632 | enumerated types (see `@enum`). | ||
633 | |||
634 | # Example | ||
635 | ```jldoctest | ||
636 | julia> @enum Color red blue green | ||
637 | |||
638 | julia> instances(Color) | ||
639 | (red::Color = 0, blue::Color = 1, green::Color = 2) | ||
640 | ``` | ||
641 | """ | ||
642 | function instances end | ||
643 | |||
644 | function to_tuple_type(@nospecialize(t)) | ||
645 | @_pure_meta | ||
646 | if isa(t,Tuple) || isa(t,AbstractArray) || isa(t,SimpleVector) | ||
647 | t = Tuple{t...} | ||
648 | end | ||
649 | if isa(t,Type) && t<:Tuple | ||
650 | for p in unwrap_unionall(t).parameters | ||
651 | if !(isa(p,Type) || isa(p,TypeVar)) | ||
652 | error("argument tuple type must contain only types") | ||
653 | end | ||
654 | end | ||
655 | else | ||
656 | error("expected tuple type") | ||
657 | end | ||
658 | t | ||
659 | end | ||
660 | |||
661 | function signature_type(@nospecialize(f), @nospecialize(args)) | ||
662 | f_type = isa(f, Type) ? Type{f} : typeof(f) | ||
663 | if isa(args, Type) | ||
664 | u = unwrap_unionall(args) | ||
665 | return rewrap_unionall(Tuple{f_type, u.parameters...}, args) | ||
666 | else | ||
667 | return Tuple{f_type, args...} | ||
668 | end | ||
669 | end | ||
670 | |||
671 | """ | ||
672 | code_lowered(f, types; generated = true) | ||
673 | |||
674 | Return an array of the lowered forms (IR) for the methods matching the given generic function | ||
675 | and type signature. | ||
676 | |||
677 | If `generated` is `false`, the returned `CodeInfo` instances will correspond to fallback | ||
678 | implementations. An error is thrown if no fallback implementation exists. | ||
679 | If `generated` is `true`, these `CodeInfo` instances will correspond to the method bodies | ||
680 | yielded by expanding the generators. | ||
681 | |||
682 | Note that an error will be thrown if `types` are not leaf types when `generated` is | ||
683 | `true` and the corresponding method is a `@generated` method. | ||
684 | """ | ||
685 | function code_lowered(@nospecialize(f), @nospecialize(t = Tuple); generated::Bool = true) | ||
686 | return map(method_instances(f, t)) do m | ||
687 | if generated && isgenerated(m) | ||
688 | if isa(m, Core.MethodInstance) | ||
689 | return Core.Compiler.get_staged(m) | ||
690 | else # isa(m, Method) | ||
691 | error("Could not expand generator for `@generated` method ", m, ". ", | ||
692 | "This can happen if the provided argument types (", t, ") are ", | ||
693 | "not leaf types, but the `generated` argument is `true`.") | ||
694 | end | ||
695 | end | ||
696 | return uncompressed_ast(m) | ||
697 | end | ||
698 | end | ||
699 | |||
700 | isgenerated(m::Method) = isdefined(m, :generator) | ||
701 | isgenerated(m::Core.MethodInstance) = isgenerated(m.def) | ||
702 | |||
703 | # low-level method lookup functions used by the compiler | ||
704 | |||
705 | unionlen(x::Union) = unionlen(x.a) + unionlen(x.b) | ||
706 | unionlen(@nospecialize(x)) = 1 | ||
707 | |||
708 | _uniontypes(x::Union, ts) = (_uniontypes(x.a,ts); _uniontypes(x.b,ts); ts) | ||
709 | _uniontypes(@nospecialize(x), ts) = (push!(ts, x); ts) | ||
710 | uniontypes(@nospecialize(x)) = _uniontypes(x, Any[]) | ||
711 | |||
712 | function _methods(@nospecialize(f), @nospecialize(t), lim::Int, world::UInt) | ||
713 | tt = signature_type(f, t) | ||
714 | return _methods_by_ftype(tt, lim, world) | ||
715 | end | ||
716 | |||
717 | function _methods_by_ftype(@nospecialize(t), lim::Int, world::UInt) | ||
718 | return _methods_by_ftype(t, lim, world, UInt[typemin(UInt)], UInt[typemax(UInt)]) | ||
719 | end | ||
720 | function _methods_by_ftype(@nospecialize(t), lim::Int, world::UInt, min::Array{UInt,1}, max::Array{UInt,1}) | ||
721 | 1 (2.70%) | 1 (2.70%) |
1 (2.70%) samples spent in _methods_by_ftype
return ccall(:jl_matching_methods, Any, (Any, Cint, Cint, UInt, Ptr{UInt}, Ptr{UInt}), t, lim, 0, world, min, max)
1 (100.00%) (ex.), 1 (100.00%) (incl.) when called from abstract_call_gf_by_type line 49 |
722 | end | ||
723 | |||
724 | # high-level, more convenient method lookup functions | ||
725 | |||
726 | # type for reflecting and pretty-printing a subset of methods | ||
727 | mutable struct MethodList | ||
728 | ms::Array{Method,1} | ||
729 | mt::Core.MethodTable | ||
730 | end | ||
731 | |||
732 | length(m::MethodList) = length(m.ms) | ||
733 | isempty(m::MethodList) = isempty(m.ms) | ||
734 | iterate(m::MethodList, s...) = iterate(m.ms, s...) | ||
735 | eltype(::Type{MethodList}) = Method | ||
736 | |||
737 | function MethodList(mt::Core.MethodTable) | ||
738 | ms = Method[] | ||
739 | visit(mt) do m | ||
740 | push!(ms, m) | ||
741 | end | ||
742 | return MethodList(ms, mt) | ||
743 | end | ||
744 | |||
745 | """ | ||
746 | methods(f, [types]) | ||
747 | |||
748 | Returns the method table for `f`. | ||
749 | |||
750 | If `types` is specified, returns an array of methods whose types match. | ||
751 | """ | ||
752 | function methods(@nospecialize(f), @nospecialize(t)) | ||
753 | if isa(f, Core.Builtin) | ||
754 | throw(ArgumentError("argument is not a generic function")) | ||
755 | end | ||
756 | t = to_tuple_type(t) | ||
757 | world = typemax(UInt) | ||
758 | return MethodList(Method[m[3] for m in _methods(f, t, -1, world)], typeof(f).name.mt) | ||
759 | end | ||
760 | |||
761 | methods(f::Core.Builtin) = MethodList(Method[], typeof(f).name.mt) | ||
762 | |||
763 | function methods_including_ambiguous(@nospecialize(f), @nospecialize(t)) | ||
764 | tt = signature_type(f, t) | ||
765 | world = typemax(UInt) | ||
766 | min = UInt[typemin(UInt)] | ||
767 | max = UInt[typemax(UInt)] | ||
768 | ms = ccall(:jl_matching_methods, Any, (Any, Cint, Cint, UInt, Ptr{UInt}, Ptr{UInt}), tt, -1, 1, world, min, max)::Array{Any,1} | ||
769 | return MethodList(Method[m[3] for m in ms], typeof(f).name.mt) | ||
770 | end | ||
771 | function methods(@nospecialize(f)) | ||
772 | # return all matches | ||
773 | return methods(f, Tuple{Vararg{Any}}) | ||
774 | end | ||
775 | |||
776 | function visit(f, mt::Core.MethodTable) | ||
777 | mt.defs !== nothing && visit(f, mt.defs) | ||
778 | nothing | ||
779 | end | ||
780 | function visit(f, mc::Core.TypeMapLevel) | ||
781 | if mc.targ !== nothing | ||
782 | e = mc.targ::Vector{Any} | ||
783 | for i in 1:length(e) | ||
784 | isassigned(e, i) && visit(f, e[i]) | ||
785 | end | ||
786 | end | ||
787 | if mc.arg1 !== nothing | ||
788 | e = mc.arg1::Vector{Any} | ||
789 | for i in 1:length(e) | ||
790 | isassigned(e, i) && visit(f, e[i]) | ||
791 | end | ||
792 | end | ||
793 | mc.list !== nothing && visit(f, mc.list) | ||
794 | mc.any !== nothing && visit(f, mc.any) | ||
795 | nothing | ||
796 | end | ||
797 | function visit(f, d::Core.TypeMapEntry) | ||
798 | while d !== nothing | ||
799 | f(d.func) | ||
800 | d = d.next | ||
801 | end | ||
802 | nothing | ||
803 | end | ||
804 | |||
805 | function length(mt::Core.MethodTable) | ||
806 | n = 0 | ||
807 | visit(mt) do m | ||
808 | n += 1 | ||
809 | end | ||
810 | return n::Int | ||
811 | end | ||
812 | isempty(mt::Core.MethodTable) = (mt.defs === nothing) | ||
813 | |||
814 | uncompressed_ast(m::Method) = isdefined(m,:source) ? uncompressed_ast(m, m.source) : | ||
815 | isdefined(m,:generator) ? error("Method is @generated; try `code_lowered` instead.") : | ||
816 | error("Code for this Method is not available.") | ||
817 | uncompressed_ast(m::Method, s::CodeInfo) = s | ||
818 | uncompressed_ast(m::Method, s::Array{UInt8,1}) = ccall(:jl_uncompress_ast, Any, (Any, Any), m, s)::CodeInfo | ||
819 | uncompressed_ast(m::Core.MethodInstance) = uncompressed_ast(m.def) | ||
820 | |||
821 | function method_instances(@nospecialize(f), @nospecialize(t), world::UInt = typemax(UInt)) | ||
822 | tt = signature_type(f, t) | ||
823 | results = Vector{Union{Method,Core.MethodInstance}}() | ||
824 | for method_data in _methods_by_ftype(tt, -1, world) | ||
825 | mtypes, msp, m = method_data | ||
826 | instance = Core.Compiler.code_for_method(m, mtypes, msp, world, false) | ||
827 | push!(results, ifelse(isa(instance, Core.MethodInstance), instance, m)) | ||
828 | end | ||
829 | return results | ||
830 | end | ||
831 | |||
832 | # this type mirrors jl_cgparams_t (documented in julia.h) | ||
833 | struct CodegenParams | ||
834 | cached::Cint | ||
835 | |||
836 | track_allocations::Cint | ||
837 | code_coverage::Cint | ||
838 | static_alloc::Cint | ||
839 | prefer_specsig::Cint | ||
840 | |||
841 | module_setup::Any | ||
842 | module_activation::Any | ||
843 | raise_exception::Any | ||
844 | emit_function::Any | ||
845 | emitted_function::Any | ||
846 | |||
847 | CodegenParams(;cached::Bool=true, | ||
848 | track_allocations::Bool=true, code_coverage::Bool=true, | ||
849 | static_alloc::Bool=true, prefer_specsig::Bool=false, | ||
850 | module_setup=nothing, module_activation=nothing, raise_exception=nothing, | ||
851 | emit_function=nothing, emitted_function=nothing) = | ||
852 | new(Cint(cached), | ||
853 | Cint(track_allocations), Cint(code_coverage), | ||
854 | Cint(static_alloc), Cint(prefer_specsig), | ||
855 | module_setup, module_activation, raise_exception, | ||
856 | emit_function, emitted_function) | ||
857 | end | ||
858 | |||
859 | # give a decent error message if we try to instantiate a staged function on non-leaf types | ||
860 | function func_for_method_checked(m::Method, @nospecialize types) | ||
861 | if isdefined(m, :generator) && !isdispatchtuple(types) | ||
862 | error("cannot call @generated function `", m, "` ", | ||
863 | "with abstract argument types: ", types) | ||
864 | end | ||
865 | return m | ||
866 | end | ||
867 | |||
868 | """ | ||
869 | code_typed(f, types; optimize=true) | ||
870 | |||
871 | Returns an array of type-inferred lowered form (IR) for the methods matching the given | ||
872 | generic function and type signature. The keyword argument `optimize` controls whether | ||
873 | additional optimizations, such as inlining, are also applied. | ||
874 | """ | ||
875 | function code_typed(@nospecialize(f), @nospecialize(types=Tuple); optimize=true) | ||
876 | ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions") | ||
877 | if isa(f, Core.Builtin) | ||
878 | throw(ArgumentError("argument is not a generic function")) | ||
879 | end | ||
880 | types = to_tuple_type(types) | ||
881 | asts = [] | ||
882 | world = ccall(:jl_get_world_counter, UInt, ()) | ||
883 | params = Core.Compiler.Params(world) | ||
884 | for x in _methods(f, types, -1, world) | ||
885 | meth = func_for_method_checked(x[3], types) | ||
886 | (code, ty) = Core.Compiler.typeinf_code(meth, x[1], x[2], optimize, params) | ||
887 | code === nothing && error("inference not successful") # inference disabled? | ||
888 | push!(asts, code => ty) | ||
889 | end | ||
890 | return asts | ||
891 | end | ||
892 | |||
893 | function return_types(@nospecialize(f), @nospecialize(types=Tuple)) | ||
894 | ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions") | ||
895 | if isa(f, Core.Builtin) | ||
896 | throw(ArgumentError("argument is not a generic function")) | ||
897 | end | ||
898 | types = to_tuple_type(types) | ||
899 | rt = [] | ||
900 | world = ccall(:jl_get_world_counter, UInt, ()) | ||
901 | params = Core.Compiler.Params(world) | ||
902 | for x in _methods(f, types, -1, world) | ||
903 | meth = func_for_method_checked(x[3], types) | ||
904 | ty = Core.Compiler.typeinf_type(meth, x[1], x[2], params) | ||
905 | ty === nothing && error("inference not successful") # inference disabled? | ||
906 | push!(rt, ty) | ||
907 | end | ||
908 | return rt | ||
909 | end | ||
910 | |||
911 | """ | ||
912 | which(f, types) | ||
913 | |||
914 | Returns the method of `f` (a `Method` object) that would be called for arguments of the given `types`. | ||
915 | |||
916 | If `types` is an abstract type, then the method that would be called by `invoke` is returned. | ||
917 | """ | ||
918 | function which(@nospecialize(f), @nospecialize(t)) | ||
919 | if isa(f, Core.Builtin) | ||
920 | throw(ArgumentError("argument is not a generic function")) | ||
921 | end | ||
922 | t = to_tuple_type(t) | ||
923 | tt = signature_type(f, t) | ||
924 | m = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), tt, typemax(UInt)) | ||
925 | if m === nothing | ||
926 | error("no unique matching method found for the specified argument types") | ||
927 | end | ||
928 | return m.func::Method | ||
929 | end | ||
930 | |||
931 | """ | ||
932 | which(module, symbol) | ||
933 | |||
934 | Return the module in which the binding for the variable referenced by `symbol` in `module` was created. | ||
935 | """ | ||
936 | function which(m::Module, s::Symbol) | ||
937 | if !isdefined(m, s) | ||
938 | error("\"$s\" is not defined in module $m") | ||
939 | end | ||
940 | return binding_module(m, s) | ||
941 | end | ||
942 | |||
943 | # function reflection | ||
944 | """ | ||
945 | nameof(f::Function) -> Symbol | ||
946 | |||
947 | Get the name of a generic `Function` as a symbol, or `:anonymous`. | ||
948 | """ | ||
949 | nameof(f::Function) = typeof(f).name.mt.name | ||
950 | |||
951 | functionloc(m::Core.MethodInstance) = functionloc(m.def) | ||
952 | |||
953 | """ | ||
954 | functionloc(m::Method) | ||
955 | |||
956 | Returns a tuple `(filename,line)` giving the location of a `Method` definition. | ||
957 | """ | ||
958 | function functionloc(m::Method) | ||
959 | ln = m.line | ||
960 | if ln <= 0 | ||
961 | error("could not determine location of method definition") | ||
962 | end | ||
963 | return (find_source_file(string(m.file)), ln) | ||
964 | end | ||
965 | |||
966 | """ | ||
967 | functionloc(f::Function, types) | ||
968 | |||
969 | Returns a tuple `(filename,line)` giving the location of a generic `Function` definition. | ||
970 | """ | ||
971 | functionloc(@nospecialize(f), @nospecialize(types)) = functionloc(which(f,types)) | ||
972 | |||
973 | function functionloc(@nospecialize(f)) | ||
974 | mt = methods(f) | ||
975 | if isempty(mt) | ||
976 | if isa(f, Function) | ||
977 | error("function has no definitions") | ||
978 | else | ||
979 | error("object is not callable") | ||
980 | end | ||
981 | end | ||
982 | if length(mt) > 1 | ||
983 | error("function has multiple methods; please specify a type signature") | ||
984 | end | ||
985 | return functionloc(first(mt)) | ||
986 | end | ||
987 | |||
988 | """ | ||
989 | parentmodule(f::Function) -> Module | ||
990 | |||
991 | Determine the module containing the (first) definition of a generic | ||
992 | function. | ||
993 | """ | ||
994 | parentmodule(f::Function) = parentmodule(typeof(f)) | ||
995 | |||
996 | """ | ||
997 | parentmodule(f::Function, types) -> Module | ||
998 | |||
999 | Determine the module containing a given definition of a generic function. | ||
1000 | """ | ||
1001 | function parentmodule(@nospecialize(f), @nospecialize(types)) | ||
1002 | m = methods(f, types) | ||
1003 | if isempty(m) | ||
1004 | error("no matching methods") | ||
1005 | end | ||
1006 | return first(m).module | ||
1007 | end | ||
1008 | |||
1009 | """ | ||
1010 | hasmethod(f, Tuple type; world = typemax(UInt)) -> Bool | ||
1011 | |||
1012 | Determine whether the given generic function has a method matching the given | ||
1013 | `Tuple` of argument types with the upper bound of world age given by `world`. | ||
1014 | |||
1015 | See also [`applicable`](@ref). | ||
1016 | |||
1017 | # Examples | ||
1018 | ```jldoctest | ||
1019 | julia> hasmethod(length, Tuple{Array}) | ||
1020 | true | ||
1021 | ``` | ||
1022 | """ | ||
1023 | function hasmethod(@nospecialize(f), @nospecialize(t); world = typemax(UInt)) | ||
1024 | t = to_tuple_type(t) | ||
1025 | t = signature_type(f, t) | ||
1026 | return ccall(:jl_method_exists, Cint, (Any, Any, UInt), typeof(f).name.mt, t, world) != 0 | ||
1027 | end | ||
1028 | |||
1029 | """ | ||
1030 | isambiguous(m1, m2; ambiguous_bottom=false) -> Bool | ||
1031 | |||
1032 | Determine whether two methods `m1` and `m2` (typically of the same | ||
1033 | function) are ambiguous. This test is performed in the context of | ||
1034 | other methods of the same function; in isolation, `m1` and `m2` might | ||
1035 | be ambiguous, but if a third method resolving the ambiguity has been | ||
1036 | defined, this returns `false`. | ||
1037 | |||
1038 | For parametric types, the `ambiguous_bottom` keyword argument controls whether | ||
1039 | `Union{}` counts as an ambiguous intersection of type parameters – when `true`, | ||
1040 | it is considered ambiguous, when `false` it is not. | ||
1041 | |||
1042 | # Examples | ||
1043 | ```jldoctest | ||
1044 | julia> foo(x::Complex{<:Integer}) = 1 | ||
1045 | foo (generic function with 1 method) | ||
1046 | |||
1047 | julia> foo(x::Complex{<:Rational}) = 2 | ||
1048 | foo (generic function with 2 methods) | ||
1049 | |||
1050 | julia> m1, m2 = collect(methods(foo)); | ||
1051 | |||
1052 | julia> typeintersect(m1.sig, m2.sig) | ||
1053 | Tuple{#foo,Complex{Union{}}} | ||
1054 | |||
1055 | julia> Base.isambiguous(m1, m2, ambiguous_bottom=true) | ||
1056 | true | ||
1057 | |||
1058 | julia> Base.isambiguous(m1, m2, ambiguous_bottom=false) | ||
1059 | false | ||
1060 | ``` | ||
1061 | """ | ||
1062 | function isambiguous(m1::Method, m2::Method; ambiguous_bottom::Bool=false) | ||
1063 | ti = typeintersect(m1.sig, m2.sig) | ||
1064 | ti === Bottom && return false | ||
1065 | if !ambiguous_bottom | ||
1066 | has_bottom_parameter(ti) && return false | ||
1067 | end | ||
1068 | ml = _methods_by_ftype(ti, -1, typemax(UInt)) | ||
1069 | isempty(ml) && return true | ||
1070 | for m in ml | ||
1071 | if ti <: m[3].sig | ||
1072 | return false | ||
1073 | end | ||
1074 | end | ||
1075 | return true | ||
1076 | end | ||
1077 | |||
1078 | """ | ||
1079 | delete_method(m::Method) | ||
1080 | |||
1081 | Make method `m` uncallable and force recompilation of any methods that use(d) it. | ||
1082 | """ | ||
1083 | function delete_method(m::Method) | ||
1084 | ccall(:jl_method_table_disable, Cvoid, (Any, Any), get_methodtable(m), m) | ||
1085 | end | ||
1086 | |||
1087 | function get_methodtable(m::Method) | ||
1088 | ft = ccall(:jl_first_argument_datatype, Any, (Any,), m.sig) | ||
1089 | (ft::DataType).name.mt | ||
1090 | end | ||
1091 | |||
1092 | """ | ||
1093 | has_bottom_parameter(t) -> Bool | ||
1094 | |||
1095 | Determine whether `t` is a Type for which one or more of its parameters is `Union{}`. | ||
1096 | """ | ||
1097 | function has_bottom_parameter(t::Type) | ||
1098 | ret = false | ||
1099 | for p in t.parameters | ||
1100 | ret |= (p == Bottom) || has_bottom_parameter(p) | ||
1101 | end | ||
1102 | ret | ||
1103 | end | ||
1104 | has_bottom_parameter(t::UnionAll) = has_bottom_parameter(unwrap_unionall(t)) | ||
1105 | has_bottom_parameter(t::Union) = has_bottom_parameter(t.a) & has_bottom_parameter(t.b) | ||
1106 | has_bottom_parameter(t::TypeVar) = t.ub == Bottom || has_bottom_parameter(t.ub) | ||
1107 | has_bottom_parameter(::Any) = false | ||
1108 | |||
1109 | min_world(m::Method) = reinterpret(UInt, m.min_world) | ||
1110 | max_world(m::Method) = reinterpret(UInt, m.max_world) | ||
1111 | min_world(m::Core.MethodInstance) = reinterpret(UInt, m.min_world) | ||
1112 | max_world(m::Core.MethodInstance) = reinterpret(UInt, m.max_world) | ||
1113 | |||
1114 | """ | ||
1115 | propertynames(x, private=false) | ||
1116 | |||
1117 | Get a tuple or a vector of the properties (`x.property`) of an object `x`. | ||
1118 | This is typically the same as [`fieldnames(typeof(x))`](@ref), but types | ||
1119 | that overload [`getproperty`](@ref) should generally overload `propertynames` | ||
1120 | as well to get the properties of an instance of the type. | ||
1121 | |||
1122 | `propertynames(x)` may return only "public" property names that are part | ||
1123 | of the documented interface of `x`. If you want it to also return "private" | ||
1124 | fieldnames intended for internal use, pass `true` for the optional second argument. | ||
1125 | REPL tab completion on `x.` shows only the `private=false` properties. | ||
1126 | """ | ||
1127 | propertynames(x) = fieldnames(typeof(x)) | ||
1128 | propertynames(m::Module) = names(m) | ||
1129 | propertynames(x, private) = propertynames(x) # ignore private flag by default |