11# This should stay as the first method because it's used in a test
22# (or change the test)
3- function checkname (fdef:: Expr , name)
4- fproto = fdef. args[1 ]
5- (fdef. head === :where || fdef. head == :(:: )) && return checkname (fproto, name)
3+ function checkname (fdef:: Expr , name) # this is now unused
64 fdef. head === :call || return false
5+ fproto = fdef. args[1 ]
76 if fproto isa Expr
8- fproto. head == :(:: ) && return last (fproto. args) == name
9- fproto. head == :curly && return fproto. args[1 ] === name
7+ fproto. head == :(:: ) && return last (fproto. args) === name # (obj::MyCallable)(x) = ...
8+ fproto. head == :curly && return fproto. args[1 ] === name # MyType{T}(x) = ...
109 # A metaprogramming-generated function
1110 fproto. head === :$ && return true # uncheckable, let's assume all is well
1211 # Is the check below redundant?
@@ -17,31 +16,128 @@ function checkname(fdef::Expr, name)
1716 isa (fproto, Symbol) || isa (fproto, QuoteNode) || isa (fproto, Expr) || return false
1817 return checkname (fproto, name)
1918end
20- checkname (fname:: Symbol , name:: Symbol ) = begin
21- fname === name && return true
22- startswith (string (name), string (' #' , fname, ' #' )) && return true
23- string (name) == string (fname, " ##kw" ) && return true
24- match (r" ^#\d +$" , string (name)) != = nothing && return true # support `f = x -> 2x`
25- return false
19+
20+ function get_call_expr (@nospecialize (ex))
21+ while isa (ex, Expr) && ex. head ∈ (:where , :(:: ))
22+ ex = ex. args[1 ]
23+ end
24+ isexpr (ex, :call ) && return ex
25+ return nothing
2626end
27- checkname (fname:: Symbol , :: Nothing ) = true
28- checkname (fname:: QuoteNode , name) = checkname (fname. value, name)
2927
30- function isfuncexpr (ex, name= nothing )
28+ function get_func_expr (@nospecialize (ex))
29+ isa (ex, Expr) || return ex
3130 # Strip any macros that wrap the method definition
32- if ex isa Expr && ex. head === :toplevel
31+ while isa (ex, Expr) && ex. head ∈ (:toplevel , :macrocall , :global , :local )
32+ ex. head == :macrocall && length (ex. args) < 3 && return ex
3333 ex = ex. args[end ]
3434 end
35- while ex isa Expr && ex. head === :macrocall && length (ex. args) >= 3
36- ex = ex. args[end ]
35+ isa (ex, Expr) || return ex
36+ if ex. head == :(= ) && length (ex. args) == 2
37+ child1, child2 = ex. args
38+ isexpr (get_call_expr (child1), :call ) && return ex
39+ isexpr (child2, :(-> )) && return child2
3740 end
41+ return ex
42+ end
43+
44+ function is_func_expr (@nospecialize (ex))
3845 isa (ex, Expr) || return false
39- if ex. head === :function || ex. head === :(= )
40- return checkname (ex. args[1 ], name)
46+ ex. head ∈ (:function , :(-> )) && return true
47+ if ex. head == :(= ) && length (ex. args) == 2
48+ child1 = ex. args[1 ]
49+ isexpr (get_call_expr (child1), :call ) && return true
4150 end
4251 return false
4352end
4453
54+ function is_func_expr (@nospecialize (ex), name:: Symbol )
55+ ex = get_func_expr (ex)
56+ is_func_expr (ex) || return false
57+ return checkname (get_call_expr (ex. args[1 ]), name)
58+ end
59+
60+ function is_func_expr (@nospecialize (ex), meth:: Method )
61+ ex = get_func_expr (ex)
62+ is_func_expr (ex) || return false
63+ fname = nothing
64+ if ex. head == :(-> )
65+ exargs = ex. args[1 ]
66+ if isexpr (exargs, :tuple )
67+ exargs = exargs. args
68+ elseif (isa (exargs, Expr) && exargs. head ∈ (:(:: ), :.)) || isa (exargs, Symbol)
69+ exargs = [exargs]
70+ elseif isa (exargs, Expr)
71+ return false
72+ end
73+ else
74+ callex = get_call_expr (ex. args[1 ])
75+ isexpr (callex, :call ) || return false
76+ fname = callex. args[1 ]
77+ modified = true
78+ while modified
79+ modified = false
80+ if isexpr (fname, :curly ) # where clause
81+ fname = fname. args[1 ]
82+ modified = true
83+ end
84+ if isexpr (fname, :., 2 ) # module-qualified
85+ fname = fname. args[2 ]
86+ @assert isa (fname, QuoteNode)
87+ fname = fname. value
88+ modified = true
89+ end
90+ if isexpr (fname, :(:: ))
91+ fname = fname. args[end ]
92+ modified = true
93+ end
94+ end
95+ if ! (isa (fname, Symbol) && is_gensym (fname)) && ! isexpr (fname, :$ )
96+ if fname === :Type && isexpr (ex. args[1 ], :where ) && isexpr (callex. args[1 ], :(:: )) && isexpr (callex. args[1 ]. args[end ], :curly )
97+ Tsym = callex. args[1 ]. args[end ]. args[2 ]
98+ for wheretyp in ex. args[1 ]. args[2 : end ]
99+ @assert isexpr (wheretyp, :(< :))
100+ if Tsym == wheretyp. args[1 ]
101+ fname = wheretyp. args[2 ]
102+ break
103+ end
104+ end
105+ end
106+ # match the function name
107+ fname === strip_gensym (meth. name) || return false
108+ end
109+ exargs = callex. args[2 : end ]
110+ end
111+ # match the argnames
112+ if ! isempty (exargs) && isexpr (first (exargs), :parameters )
113+ popfirst! (exargs) # don't match kwargs
114+ end
115+ margs = Base. method_argnames (meth)
116+ _, idx = kwmethod_basename (meth)
117+ if idx > 0
118+ margs = margs[idx: end ]
119+ end
120+ for (arg, marg) in zip (exargs, margs[2 : end ])
121+ aname = get_argname (arg)
122+ aname === :_ && continue
123+ aname === marg || (aname === Symbol (" #unused#" ) && marg === Symbol (" " )) || return false
124+ end
125+ return true # this will match any fcn `() -> ...`, but file/line is the only thing we have
126+ end
127+
128+ function get_argname (@nospecialize (ex))
129+ isa (ex, Symbol) && return ex
130+ isexpr (ex, :(:: ), 2 ) && return get_argname (ex. args[1 ]) # type-asserted
131+ isexpr (ex, :(:: ), 1 ) && return Symbol (" #unused#" ) # nameless args (e.g., `::Type{String}`)
132+ isexpr (ex, :kw ) && return get_argname (ex. args[1 ]) # default value
133+ isexpr (ex, :(= )) && return get_argname (ex. args[1 ]) # default value inside `@nospecialize`
134+ isexpr (ex, :macrocall ) && return get_argname (ex. args[end ]) # @nospecialize
135+ isexpr (ex, :... ) && return get_argname (only (ex. args)) # varargs
136+ isexpr (ex, :tuple ) && return Symbol (" " ) # tuple-destructuring
137+ dump (ex)
138+ error (" unexpected argument " , ex)
139+ end
140+
45141function linerange (def:: Expr )
46142 start, haslinestart = findline (def, identity)
47143 stop, haslinestop = findline (def, Iterators. reverse)
@@ -70,6 +166,72 @@ Base.convert(::Type{LineNumberNode}, lin::LineInfoNode) = LineNumberNode(lin.lin
70166
71167# This regex matches the pseudo-file name of a REPL history entry.
72168const rREPL = r" ^REPL\[ (\d +)\] $"
169+ # Match anonymous function names
170+ const rexfanon = r" ^#\d +$"
171+ # Match kwfunc method names
172+ const rexkwfunc = r" ^#.*##kw$"
173+
174+ is_gensym (s:: Symbol ) = is_gensym (string (s))
175+ is_gensym (str:: AbstractString ) = startswith (str, ' #' )
176+
177+ strip_gensym (s:: Symbol ) = strip_gensym (string (s))
178+ function strip_gensym (str:: AbstractString )
179+ if startswith (str, ' #' )
180+ idx = findnext (' #' , str, 2 )
181+ if idx != = nothing
182+ return Symbol (str[2 : idx- 1 ])
183+ end
184+ end
185+ endswith (str, " ##kw" ) && return Symbol (str[1 : end - 4 ])
186+ return Symbol (str)
187+ end
188+
189+ if isdefined (Core, :kwcall )
190+ is_kw_call (m:: Method ) = Base. unwrap_unionall (m. sig). parameters[1 ] === typeof (Core. kwcall)
191+ else
192+ function is_kw_call (m:: Method )
193+ T = Base. unwrap_unionall (m. sig). parameters[1 ]
194+ return match (rexkwfunc, string (T. name. name)) != = nothing
195+ end
196+ end
197+
198+ # is_body_fcn(m::Method, basename::Symbol) = match(Regex("^#$basename#\\d+\$"), string(m.name)) !== nothing
199+ # function is_body_fcn(m::Method, basename::Expr)
200+ # basename.head == :. || return false
201+ # return is_body_fcn(m, get_basename(basename))
202+ # end
203+ # is_body_fcn(m::Method, ::Nothing) = false
204+ # function get_basename(basename::Expr)
205+ # bn = basename.args[end]
206+ # @assert isa(bn, QuoteNode)
207+ # return is_body_fcn(m, bn.value)
208+ # end
209+
210+ function kwmethod_basename (meth:: Method )
211+ name = meth. name
212+ sname = string (name)
213+ mtch = match (r" ^(.*)##kw$" , sname)
214+ if mtch === nothing
215+ mtch = match (r" ^#+(.*)#" , sname)
216+ end
217+ name = mtch === nothing ? name : Symbol (only (mtch. captures))
218+ ftypname = Symbol (string (' #' , name))
219+ idx = findfirst (Base. unwrap_unionall (meth. sig). parameters) do @nospecialize (T)
220+ if isa (T, DataType)
221+ Tname = T. name. name
222+ if Tname === :Type
223+ p1 = Base. unwrap_unionall (T. parameters[1 ])
224+ Tname = isa (p1, DataType) ? p1. name. name :
225+ isa (p1, TypeVar) ? p1. name : error (" unexpected type " , typeof (p1), " for " , meth)
226+ return Tname == name
227+ end
228+ return ftypname === Tname
229+ end
230+ false
231+ end
232+ idx === nothing && return name, 0
233+ return name, idx
234+ end
73235
74236"""
75237 src = src_from_file_or_REPL(origin::AbstractString, repl = Base.active_repl)
0 commit comments