From e4770c13ded3e9611503930379bbe7defd881fc4 Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Mon, 25 May 2015 13:39:37 -0400 Subject: [PATCH] add syntax `function foo end` for generic function with no methods closes #8283 --- NEWS.md | 8 +++++-- doc/manual/functions.rst | 1 + doc/manual/methods.rst | 12 ++++++++++ src/codegen.cpp | 44 ++++++++++++++++++++++++++---------- src/interpreter.c | 2 ++ src/julia-parser.scm | 49 ++++++++++++++++++++++------------------ src/julia-syntax.scm | 14 ++++++++---- src/julia.h | 2 ++ src/toplevel.c | 24 ++++++++++++++++++++ test/core.jl | 5 ++++ 10 files changed, 120 insertions(+), 41 deletions(-) diff --git a/NEWS.md b/NEWS.md index 777fc3ca7d349..b89cf65363a19 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,7 +5,7 @@ New language features --------------------- * Function call overloading: for arbitrary objects `x` (not of type - `Function`), `x(...)` is transformed into `call(x, ...)`, and `Base.call` + `Function`), `x(...)` is transformed into `call(x, ...)`, and `call` can be overloaded as desired. Constructors are now a special case of this mechanism, which allows e.g. constructors for abstract types. `T(...)` falls back to `convert(T, x)`, so all `convert` methods implicitly @@ -23,12 +23,15 @@ New language features it operates at two different stages of evaluation. At compile time, the generated function is called with its arguments bound to the types for which it should specialize. The quoted expression it returns forms the body of the specialized - method which is then called at run time. ([#7311]). + method which is then called at run time ([#7311]). * (Also with syntax todo) Documentation system for functions, methods, types and macros in packages and user code ([#8791]). Type `?@doc` at the repl to see the current syntax and more information. + * The syntax `function foo end` can be used to introduce a generic function without + yet adding any methods ([#8283]). + Language changes ---------------- @@ -1335,6 +1338,7 @@ Too numerous to mention. [#8089]: https://github.com/JuliaLang/julia/issues/8089 [#8152]: https://github.com/JuliaLang/julia/issues/8152 [#8246]: https://github.com/JuliaLang/julia/issues/8246 +[#8283]: https://github.com/JuliaLang/julia/issues/8283 [#8297]: https://github.com/JuliaLang/julia/issues/8297 [#8399]: https://github.com/JuliaLang/julia/issues/8399 [#8423]: https://github.com/JuliaLang/julia/issues/8423 diff --git a/doc/manual/functions.rst b/doc/manual/functions.rst index f6f53652b0975..8c7166edde293 100644 --- a/doc/manual/functions.rst +++ b/doc/manual/functions.rst @@ -183,6 +183,7 @@ Expression Calls ``1:n`` :func:`colon` ``A[i]`` :func:`getindex` ``A[i]=x`` :func:`setindex!` +``A(x)`` :func:`call` =================== ================== These functions are included in the ``Base.Operators`` module even diff --git a/doc/manual/methods.rst b/doc/manual/methods.rst index 91a8eeb0caa9f..43ba174f82dd9 100644 --- a/doc/manual/methods.rst +++ b/doc/manual/methods.rst @@ -610,5 +610,17 @@ to get ``70``. ``call`` overloading is also used extensively for type constructors in Julia, discussed :ref:`later in the manual `. +Empty generic functions +----------------------- + +Occasionally it is useful to introduce a generic function without yet adding +methods. +This can be used to separate interface definitions from implementations. +It might also be done for the purpose of documentation or code readability. +The syntax for this is an empty ``function`` block without a tuple of +arguments:: + + function emptyfunc + end .. [Clarke61] Arthur C. Clarke, *Profiles of the Future* (1961): Clarke's Third Law. diff --git a/src/codegen.cpp b/src/codegen.cpp index 5a0a71c4673da..7ff9bc45d90b1 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -292,6 +292,7 @@ static Function *jlgetfield_func; static Function *jlbox_func; static Function *jlclosure_func; static Function *jlmethod_func; +static Function *jlgenericfunction_func; static Function *jlenter_func; static Function *jlleave_func; static Function *jlegal_func; @@ -1478,8 +1479,10 @@ static void simple_escape_analysis(jl_value_t *expr, bool esc, jl_codectx_t *ctx } else if (e->head == method_sym) { simple_escape_analysis(jl_exprarg(e,0), esc, ctx); - simple_escape_analysis(jl_exprarg(e,1), esc, ctx); - simple_escape_analysis(jl_exprarg(e,2), esc, ctx); + if (jl_expr_nargs(e) > 1) { + simple_escape_analysis(jl_exprarg(e,1), esc, ctx); + simple_escape_analysis(jl_exprarg(e,2), esc, ctx); + } } else if (e->head == assign_sym) { // don't consider assignment LHS as a variable "use" @@ -3121,16 +3124,22 @@ static Value *emit_expr(jl_value_t *expr, jl_codectx_t *ctx, bool isboxed, } } } - Value *a1 = boxed(emit_expr(args[1], ctx),ctx); - make_gcroot(a1, ctx); - Value *a2 = boxed(emit_expr(args[2], ctx),ctx); - make_gcroot(a2, ctx); - Value *mdargs[9] = - { name, bp, bp_owner, literal_pointer_val(bnd), a1, a2, literal_pointer_val(args[3]), - literal_pointer_val((jl_value_t*)jl_module_call_func(ctx->module)), - ConstantInt::get(T_int32, (int)iskw) }; - ctx->argDepth = last_depth; - return builder.CreateCall(prepare_call(jlmethod_func), ArrayRef(&mdargs[0], 9)); + if (jl_expr_nargs(ex) == 1) { + Value *mdargs[4] = { name, bp, bp_owner, literal_pointer_val(bnd) }; + return builder.CreateCall(prepare_call(jlgenericfunction_func), ArrayRef(&mdargs[0], 4)); + } + else { + Value *a1 = boxed(emit_expr(args[1], ctx),ctx); + make_gcroot(a1, ctx); + Value *a2 = boxed(emit_expr(args[2], ctx),ctx); + make_gcroot(a2, ctx); + Value *mdargs[9] = + { name, bp, bp_owner, literal_pointer_val(bnd), a1, a2, literal_pointer_val(args[3]), + literal_pointer_val((jl_value_t*)jl_module_call_func(ctx->module)), + ConstantInt::get(T_int32, (int)iskw) }; + ctx->argDepth = last_depth; + return builder.CreateCall(prepare_call(jlmethod_func), ArrayRef(&mdargs[0], 9)); + } } else if (head == const_sym) { jl_sym_t *sym = (jl_sym_t*)args[0]; @@ -5200,6 +5209,17 @@ static void init_julia_llvm_env(Module *m) "jl_method_def", m); add_named_global(jlmethod_func, (void*)&jl_method_def); + std::vector funcdefargs(0); + funcdefargs.push_back(jl_pvalue_llvmt); + funcdefargs.push_back(jl_ppvalue_llvmt); + funcdefargs.push_back(jl_pvalue_llvmt); + funcdefargs.push_back(jl_pvalue_llvmt); + jlgenericfunction_func = + Function::Create(FunctionType::get(jl_pvalue_llvmt, funcdefargs, false), + Function::ExternalLinkage, + "jl_generic_function_def", m); + add_named_global(jlgenericfunction_func, (void*)&jl_generic_function_def); + std::vector ehargs(0); ehargs.push_back(T_pint8); jlenter_func = diff --git a/src/interpreter.c b/src/interpreter.c index 46006808a84d2..d04d3a46bf9dc 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -294,6 +294,8 @@ static jl_value_t *eval(jl_value_t *e, jl_value_t **locals, size_t nl, size_t ng bp_owner = (jl_value_t*)jl_current_module; } } + if (jl_expr_nargs(ex) == 1) + return jl_generic_function_def(fname, bp, bp_owner, b); jl_value_t *atypes=NULL, *meth=NULL; JL_GC_PUSH2(&atypes, &meth); atypes = eval(args[1], locals, nl, ngensym); diff --git a/src/julia-parser.scm b/src/julia-parser.scm index 36981c0ea0d50..ad301af65c0d7 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -1123,28 +1123,33 @@ ((stagedfunction function macro) (if (eq? word 'stagedfunction) (syntax-deprecation-warning s "stagedfunction" "@generated function")) (let* ((paren (eqv? (require-token s) #\()) - (sig (parse-call s)) - (def (if (or (symbol? sig) - (and (pair? sig) (eq? (car sig) '|::|) - (symbol? (cadr sig)))) - (if paren - ;; in "function (x)" the (x) is a tuple - `(tuple ,sig) - ;; function foo => syntax error - (error (string "expected \"(\" in \"" word "\" definition"))) - (if (not (and (pair? sig) - (or (eq? (car sig) 'call) - (eq? (car sig) 'tuple)))) - (error (string "expected \"(\" in \"" word "\" definition")) - sig))) - (loc (begin (if (not (eq? (peek-token s) 'end)) - ;; if ends on same line, don't skip the following newline - (skip-ws-and-comments (ts:port s))) - (line-number-filename-node s))) - (body (parse-block s))) - (expect-end s) - (add-filename-to-block! body loc) - (list word def body))) + (sig (parse-call s))) + (if (and (eq? word 'function) (not paren) (symbol? sig)) + (begin (if (not (eq? (require-token s) 'end)) + (error (string "expected \"end\" in definition of function \"" sig "\""))) + (take-token s) + `(function ,sig)) + (let* ((def (if (or (symbol? sig) + (and (pair? sig) (eq? (car sig) '|::|) + (symbol? (cadr sig)))) + (if paren + ;; in "function (x)" the (x) is a tuple + `(tuple ,sig) + ;; function foo => syntax error + (error (string "expected \"(\" in " word " definition"))) + (if (not (and (pair? sig) + (or (eq? (car sig) 'call) + (eq? (car sig) 'tuple)))) + (error (string "expected \"(\" in " word " definition")) + sig))) + (loc (begin (if (not (eq? (peek-token s) 'end)) + ;; if ends on same line, don't skip the following newline + (skip-ws-and-comments (ts:port s))) + (line-number-filename-node s))) + (body (parse-block s))) + (expect-end s) + (add-filename-to-block! body loc) + (list word def body))))) ((abstract) (list 'abstract (parse-subtype-spec s))) ((type immutable) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 5718e656e3ec4..e72ceb05dbb67 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1086,7 +1086,9 @@ (expand-binding-forms `(-> ,name ,(caddr e))) e)) - e))) + (if (and (length= e 2) (symbol? name)) + `(method ,name) + e)))) ((->) (let ((a (cadr e)) @@ -3041,10 +3043,12 @@ So far only the second case can actually occur. (vinfo:set-sa! vi #f) (if (assq (car vi) captvars) (vinfo:set-iasg! vi #t))))) - `(method ,(cadr e) - ,(analyze-vars (caddr e) env captvars) - ,(analyze-vars (cadddr e) env captvars) - ,(caddddr e))) + (if (length= e 2) + `(method ,(cadr e)) + `(method ,(cadr e) + ,(analyze-vars (caddr e) env captvars) + ,(analyze-vars (cadddr e) env captvars) + ,(caddddr e)))) (else (cons (car e) (map (lambda (x) (analyze-vars x env captvars)) (cdr e))))))) diff --git a/src/julia.h b/src/julia.h index e9f8f2e4d6b69..04a633f67ed66 100644 --- a/src/julia.h +++ b/src/julia.h @@ -966,6 +966,8 @@ jl_expr_t *jl_exprn(jl_sym_t *head, size_t n); jl_function_t *jl_new_generic_function(jl_sym_t *name); void jl_add_method(jl_function_t *gf, jl_tupletype_t *types, jl_function_t *meth, jl_svec_t *tvars, int8_t isstaged); +DLLEXPORT jl_value_t *jl_generic_function_def(jl_sym_t *name, jl_value_t **bp, jl_value_t *bp_owner, + jl_binding_t *bnd); DLLEXPORT jl_value_t *jl_method_def(jl_sym_t *name, jl_value_t **bp, jl_value_t *bp_owner, jl_binding_t *bnd, jl_svec_t *argtypes, jl_function_t *f, jl_value_t *isstaged, jl_value_t *call_func, int iskw); diff --git a/src/toplevel.c b/src/toplevel.c index ff115071e8fa8..0dbda6a304f4d 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -660,6 +660,30 @@ static int type_contains(jl_value_t *ty, jl_value_t *x) void print_func_loc(JL_STREAM *s, jl_lambda_info_t *li); +// empty generic function def +// TODO: maybe have jl_method_def call this +DLLEXPORT jl_value_t *jl_generic_function_def(jl_sym_t *name, jl_value_t **bp, jl_value_t *bp_owner, + jl_binding_t *bnd) +{ + jl_value_t *gf=NULL; + + if (bnd && bnd->value != NULL && !bnd->constp) + jl_errorf("cannot define function %s; it already has a value", bnd->name->name); + if (*bp != NULL) { + gf = *bp; + if (!jl_is_gf(gf)) + jl_errorf("cannot define function %s; it already has a value", name->name); + } + if (bnd) + bnd->constp = 1; + if (*bp == NULL) { + gf = (jl_value_t*)jl_new_generic_function(name); + *bp = gf; + if (bp_owner) gc_wb(bp_owner, gf); + } + return gf; +} + DLLEXPORT jl_value_t *jl_method_def(jl_sym_t *name, jl_value_t **bp, jl_value_t *bp_owner, jl_binding_t *bnd, jl_svec_t *argdata, jl_function_t *f, jl_value_t *isstaged, diff --git a/test/core.jl b/test/core.jl index dafcfe15cda23..b5521e2b3fdef 100644 --- a/test/core.jl +++ b/test/core.jl @@ -2899,3 +2899,8 @@ let t = Tuple{Type{Vector{Int}}} t = Tuple{Type{Dict{TypeVar(:K, true)}}} @test f11355(t) == 100 end + +# issue #8283 +function func8283 end +@test isa(func8283,Function) && isgeneric(func8283) +@test_throws MethodError func8283()