Skip to content

Commit

Permalink
add syntax function foo end for generic function with no methods
Browse files Browse the repository at this point in the history
  • Loading branch information
JeffBezanson authored and tkelman committed Jun 6, 2015
1 parent 5e008bd commit e4770c1
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 41 deletions.
8 changes: 6 additions & 2 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
----------------

Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions doc/manual/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions doc/manual/methods.rst
Original file line number Diff line number Diff line change
Expand Up @@ -610,5 +610,17 @@ to get ``70``.
``call`` overloading is also used extensively for type constructors in
Julia, discussed :ref:`later in the manual <constructors-call-and-conversion>`.

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.
44 changes: 32 additions & 12 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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<Value*>(&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<Value*>(&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<Value*>(&mdargs[0], 9));
}
}
else if (head == const_sym) {
jl_sym_t *sym = (jl_sym_t*)args[0];
Expand Down Expand Up @@ -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<Type*> 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<Type*> ehargs(0);
ehargs.push_back(T_pint8);
jlenter_func =
Expand Down
2 changes: 2 additions & 0 deletions src/interpreter.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
49 changes: 27 additions & 22 deletions src/julia-parser.scm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 9 additions & 5 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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)))))))
Expand Down
2 changes: 2 additions & 0 deletions src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
24 changes: 24 additions & 0 deletions src/toplevel.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions test/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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()

0 comments on commit e4770c1

Please sign in to comment.