Skip to content

Commit 7f238f9

Browse files
committed
Use a curried helper for module-local eval/include
In #55466, the automatically added `include` method for non-bare modules was adjusted to conform the signature to the version of those methods in Main (defined in sysimg.jl, since `Main` is technically a bare module). Unfortunately, this broke some downstream packages which overload Base.include with additional functionality and got broken by the additional type restriction. The motivation in #55466 was to give a slightly nicer MethodError. While I don't think this is per-se a particularly strong justification, I do agree that it's awkward for the `Main` version of these functions to have (marginally) different behavior than the version of these functions that gets introduced automatically in new modules (Which has been the case ever since [1], which added the AbstractString restriction in `Main`, but not in the auto-generated versions). This is particularly true, because we use the `Main` version to document the auto-introduction of these methods, which has regularly been a point of confusion. This PR tries to address this problem once and for all, but just not generating special methods into every new module. Instead, there are curried helpers for eval and include in Core and Base (respectively), which can be added to a module simply by doing `const include = IncludeInto(MyModule)` (and similarly for `eval`). As before, this happens automatically for non-bare modules. It thus conforms the behavior of the `Main` version of these functions and the auto-generated versions by construction. Additionally, it saves us having to generate all the additional code/types/objects, etc associated with having extra generic functions in each new module. The impact of this isn't huge, because there aren't that many modules, but it feels conceptually nicer. There is a little bit of extra work in this PR because we have special snowflake backtrace printing code for the `include` machinery, which needs adjusting, but other than that the change is straightforward. [1] 957848b
1 parent 8bae3ee commit 7f238f9

File tree

11 files changed

+61
-45
lines changed

11 files changed

+61
-45
lines changed

base/Base.jl

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ function include(mod::Module, path::String)
2525
end
2626
include(path::String) = include(Base, path)
2727

28+
struct IncludeInto <: Function
29+
m::Module
30+
end
31+
(this::IncludeInto)(fname::AbstractString) = include(this.m, fname)
32+
2833
# from now on, this is now a top-module for resolving syntax
2934
const is_primary_base_module = ccall(:jl_module_parent, Ref{Module}, (Any,), Base) === Core.Main
3035
ccall(:jl_set_istopmod, Cvoid, (Any, Bool), Base, is_primary_base_module)
@@ -572,15 +577,20 @@ include("precompilation.jl")
572577
for m in methods(include)
573578
delete_method(m)
574579
end
580+
for m in methods(IncludeInto(Base))
581+
delete_method(m)
582+
end
575583

576584
# This method is here only to be overwritten during the test suite to test
577585
# various sysimg related invalidation scenarios.
578586
a_method_to_overwrite_in_test() = inferencebarrier(1)
579587

580588
# These functions are duplicated in client.jl/include(::String) for
581589
# nicer stacktraces. Modifications here have to be backported there
582-
include(mod::Module, _path::AbstractString) = _include(identity, mod, _path)
583-
include(mapexpr::Function, mod::Module, _path::AbstractString) = _include(mapexpr, mod, _path)
590+
@noinline include(mod::Module, _path::AbstractString) = _include(identity, mod, _path)
591+
@noinline include(mapexpr::Function, mod::Module, _path::AbstractString) = _include(mapexpr, mod, _path)
592+
(this::IncludeInto)(fname::AbstractString) = include(identity, this.m, fname)
593+
(this::IncludeInto)(mapexpr::Function, fname::AbstractString) = include(mapexpr, this.m, fname)
584594

585595
# External libraries vendored into Base
586596
Core.println("JuliaSyntax/src/JuliaSyntax.jl")

base/boot.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,9 +454,13 @@ Nothing() = nothing
454454
# This should always be inlined
455455
getptls() = ccall(:jl_get_ptls_states, Ptr{Cvoid}, ())
456456

457-
include(m::Module, fname::String) = ccall(:jl_load_, Any, (Any, Any), m, fname)
457+
include(m::Module, fname::String) = (@noinline; ccall(:jl_load_, Any, (Any, Any), m, fname))
458+
eval(m::Module, @nospecialize(e)) = (@noinline; ccall(:jl_toplevel_eval_in, Any, (Any, Any), m, e))
458459

459-
eval(m::Module, @nospecialize(e)) = ccall(:jl_toplevel_eval_in, Any, (Any, Any), m, e)
460+
struct EvalInto <: Function
461+
m::Module
462+
end
463+
(this::EvalInto)(@nospecialize(e)) = eval(this.m, e)
460464

461465
mutable struct Box
462466
contents::Any

base/docs/basedocs.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2580,7 +2580,7 @@ cases.
25802580
See also [`setproperty!`](@ref Base.setproperty!) and [`getglobal`](@ref)
25812581
25822582
# Examples
2583-
```jldoctest; filter = r"Stacktrace:(\\n \\[[0-9]+\\].*)*"
2583+
```jldoctest; filter = r"Stacktrace:(\\n \\[[0-9]+\\].*\\n.*)*"
25842584
julia> module M; global a; end;
25852585
25862586
julia> M.a # same as `getglobal(M, :a)`

base/errorshow.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,10 @@ function _simplify_include_frames(trace)
850850
for i in length(trace):-1:1
851851
frame::StackFrame, _ = trace[i]
852852
mod = parentmodule(frame)
853-
if first_ignored === nothing
853+
if mod === Base && frame.func === :IncludeInto ||
854+
mod === Core && frame.func === :EvalInto
855+
kept_frames[i] = false
856+
elseif first_ignored === nothing
854857
if mod === Base && frame.func === :_include
855858
# Hide include() machinery by default
856859
first_ignored = i

base/loading.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2890,12 +2890,12 @@ julia> rm("testfile.jl")
28902890
```
28912891
"""
28922892
function evalfile(path::AbstractString, args::Vector{String}=String[])
2893-
return Core.eval(Module(:__anon__),
2893+
m = Module(:__anon__)
2894+
return Core.eval(m,
28942895
Expr(:toplevel,
28952896
:(const ARGS = $args),
2896-
:(eval(x) = $(Expr(:core, :eval))(__anon__, x)),
2897-
:(include(x) = $(Expr(:top, :include))(__anon__, x)),
2898-
:(include(mapexpr::Function, x) = $(Expr(:top, :include))(mapexpr, __anon__, x)),
2897+
:(const include = $(Base.IncludeInto(m))),
2898+
:(const eval = $(Core.EvalInto(m))),
28992899
:(include($path))))
29002900
end
29012901
evalfile(path::AbstractString, args::Vector) = evalfile(path, String[args...])

base/show.jl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3418,3 +3418,22 @@ function show(io::IO, ::MIME"text/plain", bnd::Core.Binding)
34183418
end
34193419
end
34203420
end
3421+
3422+
# Special pretty printing for EvalInto/IncludeInto
3423+
function show(io::IO, ii::IncludeInto)
3424+
if getglobal(ii.m, :include) === ii
3425+
print(io, ii.m)
3426+
print(io, ".include")
3427+
else
3428+
show_default(io, ii)
3429+
end
3430+
end
3431+
3432+
function show(io::IO, ei::Core.EvalInto)
3433+
if getglobal(ei.m, :eval) === ei
3434+
print(io, ei.m)
3435+
print(io, ".eval")
3436+
else
3437+
show_default(io, ei)
3438+
end
3439+
end

base/sysimg.jl

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,7 @@ Use [`Base.include`](@ref) to evaluate a file into another module.
3232
!!! compat "Julia 1.5"
3333
Julia 1.5 is required for passing the `mapexpr` argument.
3434
"""
35-
include(mapexpr::Function, fname::AbstractString) = Base._include(mapexpr, Main, fname)
36-
function include(fname::AbstractString)
37-
isa(fname, String) || (fname = Base.convert(String, fname)::String)
38-
Base._include(identity, Main, fname)
39-
end
35+
const include = Base.IncludeInto(Main)
4036

4137
"""
4238
eval(expr)
@@ -45,7 +41,7 @@ Evaluate an expression in the global scope of the containing module.
4541
Every `Module` (except those defined with `baremodule`) has its own 1-argument
4642
definition of `eval`, which evaluates expressions in that module.
4743
"""
48-
eval(x) = Core.eval(Main, x)
44+
const eval = Core.EvalInto(Main)
4945

5046
# Ensure this file is also tracked
5147
pushfirst!(Base._included_files, (@__MODULE__, abspath(@__FILE__)))

src/jlfrontend.scm

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -199,28 +199,6 @@
199199
(error-wrap (lambda ()
200200
(julia-expand-macroscope expr))))
201201

202-
;; construct default definitions of `eval` for non-bare modules
203-
;; called by jl_eval_module_expr
204-
(define (module-default-defs name file line)
205-
(jl-expand-to-thunk
206-
(let* ((loc (if (and (eq? file 'none) (eq? line 0)) '() `((line ,line ,file))))
207-
(x (if (eq? name 'x) 'y 'x))
208-
(mex (if (eq? name 'mapexpr) 'map_expr 'mapexpr)))
209-
`(block
210-
(= (call eval ,x)
211-
(block
212-
,@loc
213-
(call (core eval) ,name ,x)))
214-
(= (call include ,x)
215-
(block
216-
,@loc
217-
(call (core _call_latest) (top include) ,name ,x)))
218-
(= (call include (:: ,mex (top Function)) ,x)
219-
(block
220-
,@loc
221-
(call (core _call_latest) (top include) ,mex ,name ,x)))))
222-
file line))
223-
224202
; run whole frontend on a string. useful for testing.
225203
(define (fe str)
226204
(expand-toplevel-expr (julia-parse str) 'none 0))

src/toplevel.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,17 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex
206206
if (std_imports) {
207207
if (jl_base_module != NULL) {
208208
jl_add_standard_imports(newm);
209+
jl_datatype_t *include_into = (jl_datatype_t *)jl_get_global(jl_base_module, jl_symbol("IncludeInto"));
210+
if (include_into) {
211+
form = jl_new_struct(include_into, newm);
212+
jl_set_const(newm, jl_symbol("include"), form);
213+
}
214+
}
215+
jl_datatype_t *eval_into = (jl_datatype_t *)jl_get_global(jl_core_module, jl_symbol("EvalInto"));
216+
if (eval_into) {
217+
form = jl_new_struct(eval_into, newm);
218+
jl_set_const(newm, jl_symbol("eval"), form);
209219
}
210-
// add `eval` function
211-
form = jl_call_scm_on_ast_and_loc("module-default-defs", (jl_value_t*)name, newm, filename, lineno);
212-
jl_toplevel_eval_flex(newm, form, 0, 1, &filename, &lineno);
213-
form = NULL;
214220
}
215221

216222
newm->file = jl_symbol(filename);

test/docs.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ end
101101

102102
@test Docs.undocumented_names(_ModuleWithUndocumentedNames) == [Symbol("@foo"), :f, :]
103103
@test isempty(Docs.undocumented_names(_ModuleWithSomeDocumentedNames))
104-
@test Docs.undocumented_names(_ModuleWithSomeDocumentedNames; private=true) == [:eval, :g, :include]
104+
@test Docs.undocumented_names(_ModuleWithSomeDocumentedNames; private=true) == [:g]
105105

106106

107107
# issue #11548

0 commit comments

Comments
 (0)