diff --git a/Compiler/src/ssair/ir.jl b/Compiler/src/ssair/ir.jl index 743e26cb230bb..db819ec9ae67b 100644 --- a/Compiler/src/ssair/ir.jl +++ b/Compiler/src/ssair/ir.jl @@ -583,7 +583,7 @@ function is_relevant_expr(e::Expr) :gc_preserve_begin, :gc_preserve_end, :foreigncall, :isdefined, :copyast, :throw_undef_if_not, - :cfunction, :method, :pop_exception, + :cfunction, :pop_exception, :leave, :new_opaque_closure) end diff --git a/Compiler/src/ssair/verify.jl b/Compiler/src/ssair/verify.jl index e190ae7a8438f..07771437a1234 100644 --- a/Compiler/src/ssair/verify.jl +++ b/Compiler/src/ssair/verify.jl @@ -25,7 +25,7 @@ if !isdefined(@__MODULE__, Symbol("@verify_error")) end end -is_toplevel_expr_head(head::Symbol) = head === :method || head === :thunk +is_toplevel_expr_head(head::Symbol) = head === :thunk is_value_pos_expr_head(head::Symbol) = head === :static_parameter function check_op(ir::IRCode, domtree::DomTree, @nospecialize(op), use_bb::Int, use_idx::Int, printed_use_idx::Int, print::Bool, isforeigncall::Bool, arg_idx::Int, allow_frontend_forms::Bool, @nospecialize(raise_error)) diff --git a/Compiler/src/validation.jl b/Compiler/src/validation.jl index 3fce8da4e6dd0..e5ce60de689ae 100644 --- a/Compiler/src/validation.jl +++ b/Compiler/src/validation.jl @@ -8,7 +8,6 @@ const VALID_EXPR_HEADS = IdDict{Symbol,UnitRange{Int}}( :static_parameter => 1:1, :(&) => 1:1, :(=) => 2:2, - :method => 1:4, :new => 1:typemax(Int), :splatnew => 2:2, :the_exception => 0:0, @@ -49,7 +48,6 @@ const SLOTFLAGS_MISMATCH = "length(slotnames) < length(slotflags)" const SSAVALUETYPES_MISMATCH = "not all SSAValues in AST have a type in ssavaluetypes" const SSAVALUETYPES_MISMATCH_UNINFERRED = "uninferred CodeInfo ssavaluetypes field does not equal the number of present SSAValues" const SSAFLAGS_MISMATCH = "not all SSAValues have a corresponding `ssaflags`" -const NON_TOP_LEVEL_METHOD = "encountered `Expr` head `:method` in non-top-level code (i.e. `nargs` > 0)" const SIGNATURE_NARGS_MISMATCH = "method signature does not match number of method arguments" const SLOTNAMES_NARGS_MISMATCH = "CodeInfo for method contains fewer slotnames than the number of method arguments" const INVALID_SIGNATURE_OPAQUE_CLOSURE = "invalid signature of method for opaque closure - `sig` field must always be set to `Tuple`" @@ -119,9 +117,6 @@ function validate_code!(errors::Vector{InvalidCodeError}, c::CodeInfo, is_top_le for x in c.code if isa(x, Expr) head = x.head - if !is_top_level - head === :method && push!(errors, InvalidCodeError(NON_TOP_LEVEL_METHOD)) - end narg_bounds = get(VALID_EXPR_HEADS, head, -1:-1) nargs = length(x.args) if narg_bounds == -1:-1 @@ -145,7 +140,7 @@ function validate_code!(errors::Vector{InvalidCodeError}, c::CodeInfo, is_top_le head === :gc_preserve_end || head === :meta || head === :inbounds || head === :foreigncall || head === :cfunction || head === :leave || head === :pop_exception || - head === :method || head === :static_parameter || + head === :static_parameter || head === :new || head === :splatnew || head === :thunk || head === :loopinfo || head === :throw_undef_if_not || head === :code_coverage_effect || head === :inline || head === :noinline validate_val!(x) diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index c4dcc90baf945..ac53b005d1c00 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -2816,6 +2816,24 @@ See also [`const`](@ref). """ Core.declare_const +""" + define_method(module::Module, name::Symbol) + define_method(module::Module, fname_or_mt, argdata, code) + +Define a method. The 2-argument form declares a new generic function with the given `name`. +The 4-argument form defines a method for a function, where `fname_or_mt` is either the function +or its method table, `argdata` is the signature information, and `code` is the method body. + +This function is typically generated by lowering `function` definitions and should not +be called directly in most cases. + +!!! compat "Julia 1.14" + This function requires Julia 1.14 or later. + +See also [`function`](@ref). +""" +Core.define_method + """ _import(to::Module, from::Module, asname::Symbol, [sym::Symbol, imported::Bool]) diff --git a/src/builtin_proto.h b/src/builtin_proto.h index ff634149a06f6..24dc05f4c5c06 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -36,6 +36,7 @@ extern "C" { XX(getfield,"getfield") \ XX(getglobal,"getglobal") \ XX(declare_global,"declare_global") \ + XX(define_method,"define_method") \ XX(ifelse,"ifelse") \ XX(intrinsic_call,"intrinsic_call") \ XX(invoke,"invoke") \ diff --git a/src/builtins.c b/src/builtins.c index b50e6c247ce46..ee98194471c17 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1547,6 +1547,32 @@ JL_CALLABLE(jl_f_declare_const) return nargs > 2 ? args[2] : jl_nothing; } +// define_method(module::Module, name::Symbol) - declare generic function +// define_method(module::Module, fname_or_mt, argdata, code) - define method +JL_CALLABLE(jl_f_define_method) +{ + JL_NARGS(define_method, 2, 4); + JL_TYPECHK(define_method, module, args[0]); + jl_module_t *module = (jl_module_t *)args[0]; + + // Generic function declaration: define_method(module, name) + if (nargs == 2) { + JL_TYPECHK(define_method, symbol, args[1]); + jl_sym_t *fname = (jl_sym_t*)args[1]; + return jl_declare_const_gf(module, fname); + } + + // Method definition: define_method(module, fname_or_mt, argdata, code) + jl_value_t *fname = args[1]; + jl_methtable_t *mt = NULL; + if (jl_is_mtable(fname)) + mt = (jl_methtable_t*)fname; + jl_value_t *atypes = args[2]; + jl_value_t *meth = args[3]; + jl_method_t *ret = jl_method_def((jl_svec_t*)atypes, mt, (jl_code_info_t*)meth, module); + return (jl_value_t *)ret; +} + // import, using -------------------------------------------------------------- // Import binding `from.sym` as `asname` into `to`: diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index f3a57c6699c08..4b54ff3766986 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -5094,7 +5094,8 @@ f(x) = yt(x) (error (string "Global method definition" (linenode-string current-loc) " needs to be placed at the top level, or use \"eval\"."))) (if (length> e 2) - (let* ((sig (let ((sig (compile (caddr e) break-labels #t #f))) + (let* ((name (cadr e)) + (sig (let ((sig (compile (caddr e) break-labels #t #f))) (if (valid-ir-argument? sig) sig (let ((l (make-ssavalue))) @@ -5107,12 +5108,34 @@ f(x) = yt(x) (emit `(= ,l ,(compile lam break-labels #t #f))) l)))) (let ((val (make-ssavalue))) - (emit `(= ,val (method ,(or (cadr e) '(false)) ,sig ,lam))) + (emit `(= ,val ,(cond ((not name) + `(call (core define_method) (thismodule) (false) ,sig ,lam)) + ((globalref? name) + `(call (core define_method) ,(cadr name) (inert ,(caddr name)) ,sig ,lam)) + ((symbol? name) + `(call (core define_method) (thismodule) (inert ,name) ,sig ,lam)) + (else + `(call (core define_method) (thismodule) ,name ,sig ,lam))))) + (if (null? (cadr lam)) (emit `(latestworld))) (if tail (emit-return tail val)) val)) - (cond (tail (emit-return tail e)) - (value e) - (else (emit e))))) + ;; Generic function declaration (short form) + (let ((name (cadr e))) + (if value + (let ((val (make-ssavalue))) + (emit `(= ,val ,(if (globalref? name) + `(call (core define_method) ,(cadr name) (inert ,(caddr name))) + `(call (core define_method) (thismodule) (inert ,name))))) + (if (null? (cadr lam)) (emit `(latestworld))) + (if tail (emit-return tail val)) + val) + (begin + (emit ,(if (globalref? name) + `(call (core define_method) ,(cadr name) (inert ,(caddr name))) + `(call (core define_method) (thismodule) (inert ,name)))) + (if (null? (cadr lam)) (emit `(latestworld))) + (if tail (emit-return tail '(null))) + '(null)))))) ((lambda) (let ((temp (linearize e))) (cond (tail (emit-return tail temp)) diff --git a/src/toplevel.c b/src/toplevel.c index 4622a9e8b4ce0..333c2c3b6b836 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -382,7 +382,7 @@ static void expr_attributes(jl_value_t *v, jl_array_t *body, int *has_ccall, int *has_defs = 1; return; } - else if (head == jl_method_sym || jl_is_toplevel_only_expr(v)) { + else if (jl_is_toplevel_only_expr(v)) { *has_defs = 1; } else if (head == jl_cfunction_sym) { @@ -417,7 +417,7 @@ static void expr_attributes(jl_value_t *v, jl_array_t *body, int *has_ccall, int *has_ccall = 1; } // TODO: rely on latestworld instead of function callee detection here (or add it to jl_is_toplevel_only_expr) - if (called == BUILTIN(_typebody) || called == BUILTIN(declare_const)) { + if (called == BUILTIN(_typebody) || called == BUILTIN(declare_const) || called == BUILTIN(define_method)) { *has_defs = 1; } } @@ -489,7 +489,7 @@ int jl_needs_lowering(jl_value_t *e) JL_NOTSAFEPOINT jl_sym_t *head = ex->head; if (head == jl_module_sym || head == jl_export_sym || head == jl_public_sym || head == jl_thunk_sym || head == jl_toplevel_sym || head == jl_error_sym || - head == jl_incomplete_sym || head == jl_method_sym) { + head == jl_incomplete_sym) { return 0; } return 1; diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index ee40ebdd4abad..4fc293dee2ef6 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -1454,6 +1454,46 @@ function deserialize_expr(s::AbstractSerializer, len) resolve_ref_immediately(s, e) e.head = deserialize(s)::Symbol e.args = Any[ deserialize(s) for i = 1:len ] + + # Rewrite old :method expressions to define_method calls + if e.head === :method + mod = current_module[] + if mod === nothing + # No current module context, keep the :method expression and hope for the best + # This shouldn't happen in practice for deserialization of top-level code + return e + end + + if len == 1 + # Short form: (:method name) → (call Core.define_method module (inert name)) + name = e.args[1] + if name isa GlobalRef + # Extract module and name from GlobalRef + mod_ref = name.mod + sym = name.name + e = Expr(:call, GlobalRef(Core, :define_method), mod_ref, QuoteNode(sym)) + else + # Simple symbol - use the current module + e = Expr(:call, GlobalRef(Core, :define_method), mod, QuoteNode(name)) + end + elseif len == 3 + # Long form: (:method name_or_mt sigtype code) → (call Core.define_method module name_or_mt sigtype code) + name_or_mt = e.args[1] + sigtype = e.args[2] + code = e.args[3] + if name_or_mt isa Symbol + name_or_mt = QuoteNode(name_or_mt) + e = Expr(:call, GlobalRef(Core, :define_method), mod, name_or_mt, sigtype, code) + elseif name_or_mt isa GlobalRef + mod_ref = name_or_mt.mod + sym = name_or_mt.name + e = Expr(:call, GlobalRef(Core, :define_method), mod_ref, QuoteNode(sym), sigtype, code) + else + # name_or_mt is already something else (like false or a method table) + e = Expr(:call, GlobalRef(Core, :define_method), mod, name_or_mt, sigtype, code) + end + end + end e end diff --git a/test/syntax.jl b/test/syntax.jl index cec389be057ad..b33afbe7f42a9 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -566,8 +566,10 @@ end let code = Meta.lower(Main, :(@inline f(p::Int=2) = 3)).args[1].code local src for i = length(code):-1:1 - if Meta.isexpr(code[i], :method) - src = code[i].args[3] + if Meta.isexpr(code[i], :call) && length(code[i].args) >= 5 && + code[i].args[1] isa GlobalRef && code[i].args[1].name == :define_method && + code[i].args[5] isa Core.CodeInfo + src = code[i].args[5] break end end @@ -3988,8 +3990,16 @@ end code = src.args[1].code for i = length(code):-1:1 expr = code[i] - Meta.isexpr(expr, :method) || continue - @test isa(expr.args[1], Union{GlobalRef, Symbol}) + if Meta.isexpr(expr, :call) && length(expr.args) >= 3 && + expr.args[1] isa GlobalRef && expr.args[1].name == :define_method + # args[3] should be a QuoteNode wrapping a Symbol, or a GlobalRef + name_arg = expr.args[3] + if name_arg isa QuoteNode + @test isa(name_arg.value, Symbol) + else + @test isa(name_arg, GlobalRef) + end + end end end @@ -4371,10 +4381,11 @@ module DoubleImport end @test DoubleImport.Random === Test.Random -# Expr(:method) returns the method +# define_method call returns the method let ex = @Meta.lower function return_my_method(); 1; end code = ex.args[1].code - idx = findfirst(ex->Meta.isexpr(ex, :method) && length(ex.args) > 1, code) + idx = findfirst(ex->Meta.isexpr(ex, :call) && length(ex.args) >= 5 && + ex.args[1] isa GlobalRef && ex.args[1].name == :define_method, code) code[end] = Core.ReturnNode(Core.SSAValue(idx)) @test isa(Core.eval(@__MODULE__, ex), Method) end