Skip to content

Commit 275d404

Browse files
committed
add syntax function foo end for generic function with no methods
closes #8283
1 parent 52e4f3a commit 275d404

File tree

10 files changed

+120
-41
lines changed

10 files changed

+120
-41
lines changed

NEWS.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ New language features
55
---------------------
66

77
* Function call overloading: for arbitrary objects `x` (not of type
8-
`Function`), `x(...)` is transformed into `call(x, ...)`, and `Base.call`
8+
`Function`), `x(...)` is transformed into `call(x, ...)`, and `call`
99
can be overloaded as desired. Constructors are now a special case of
1010
this mechanism, which allows e.g. constructors for abstract types.
1111
`T(...)` falls back to `convert(T, x)`, so all `convert` methods implicitly
@@ -23,12 +23,15 @@ New language features
2323
it operates at two different stages of evaluation. At compile time, the generated
2424
function is called with its arguments bound to the types for which it should
2525
specialize. The quoted expression it returns forms the body of the specialized
26-
method which is then called at run time. ([#7311]).
26+
method which is then called at run time ([#7311]).
2727

2828
* (Also with syntax todo) Documentation system for functions, methods, types
2929
and macros in packages and user code ([#8791]). Type `?@doc` at the repl
3030
to see the current syntax and more information.
3131

32+
* The syntax `function foo end` can be used to introduce a generic function without
33+
yet adding any methods ([#8283]).
34+
3235
Language changes
3336
----------------
3437

@@ -1324,6 +1327,7 @@ Too numerous to mention.
13241327
[#8089]: https://github.com/JuliaLang/julia/issues/8089
13251328
[#8152]: https://github.com/JuliaLang/julia/issues/8152
13261329
[#8246]: https://github.com/JuliaLang/julia/issues/8246
1330+
[#8283]: https://github.com/JuliaLang/julia/issues/8283
13271331
[#8297]: https://github.com/JuliaLang/julia/issues/8297
13281332
[#8399]: https://github.com/JuliaLang/julia/issues/8399
13291333
[#8423]: https://github.com/JuliaLang/julia/issues/8423

doc/manual/functions.rst

+1
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ Expression Calls
183183
``1:n`` :func:`colon`
184184
``A[i]`` :func:`getindex`
185185
``A[i]=x`` :func:`setindex!`
186+
``A(x)`` :func:`call`
186187
=================== ==================
187188

188189
These functions are included in the ``Base.Operators`` module even

doc/manual/methods.rst

+12
Original file line numberDiff line numberDiff line change
@@ -610,5 +610,17 @@ to get ``70``.
610610
``call`` overloading is also used extensively for type constructors in
611611
Julia, discussed :ref:`later in the manual <constructors-call-and-conversion>`.
612612

613+
Empty generic functions
614+
-----------------------
615+
616+
Occasionally it is useful to introduce a generic function without yet adding
617+
methods.
618+
This can be used to separate interface definitions from implementations.
619+
It might also be done for the purpose of documentation or code readability.
620+
The syntax for this is an empty ``function`` block without a tuple of
621+
arguments::
622+
623+
function emptyfunc
624+
end
613625

614626
.. [Clarke61] Arthur C. Clarke, *Profiles of the Future* (1961): Clarke's Third Law.

src/codegen.cpp

+32-12
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ static Function *jlgetfield_func;
292292
static Function *jlbox_func;
293293
static Function *jlclosure_func;
294294
static Function *jlmethod_func;
295+
static Function *jlgenericfunction_func;
295296
static Function *jlenter_func;
296297
static Function *jlleave_func;
297298
static Function *jlegal_func;
@@ -1478,8 +1479,10 @@ static void simple_escape_analysis(jl_value_t *expr, bool esc, jl_codectx_t *ctx
14781479
}
14791480
else if (e->head == method_sym) {
14801481
simple_escape_analysis(jl_exprarg(e,0), esc, ctx);
1481-
simple_escape_analysis(jl_exprarg(e,1), esc, ctx);
1482-
simple_escape_analysis(jl_exprarg(e,2), esc, ctx);
1482+
if (jl_expr_nargs(e) > 1) {
1483+
simple_escape_analysis(jl_exprarg(e,1), esc, ctx);
1484+
simple_escape_analysis(jl_exprarg(e,2), esc, ctx);
1485+
}
14831486
}
14841487
else if (e->head == assign_sym) {
14851488
// 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,
31213124
}
31223125
}
31233126
}
3124-
Value *a1 = boxed(emit_expr(args[1], ctx),ctx);
3125-
make_gcroot(a1, ctx);
3126-
Value *a2 = boxed(emit_expr(args[2], ctx),ctx);
3127-
make_gcroot(a2, ctx);
3128-
Value *mdargs[9] =
3129-
{ name, bp, bp_owner, literal_pointer_val(bnd), a1, a2, literal_pointer_val(args[3]),
3130-
literal_pointer_val((jl_value_t*)jl_module_call_func(ctx->module)),
3131-
ConstantInt::get(T_int32, (int)iskw) };
3132-
ctx->argDepth = last_depth;
3133-
return builder.CreateCall(prepare_call(jlmethod_func), ArrayRef<Value*>(&mdargs[0], 9));
3127+
if (jl_expr_nargs(ex) == 1) {
3128+
Value *mdargs[4] = { name, bp, bp_owner, literal_pointer_val(bnd) };
3129+
return builder.CreateCall(prepare_call(jlgenericfunction_func), ArrayRef<Value*>(&mdargs[0], 4));
3130+
}
3131+
else {
3132+
Value *a1 = boxed(emit_expr(args[1], ctx),ctx);
3133+
make_gcroot(a1, ctx);
3134+
Value *a2 = boxed(emit_expr(args[2], ctx),ctx);
3135+
make_gcroot(a2, ctx);
3136+
Value *mdargs[9] =
3137+
{ name, bp, bp_owner, literal_pointer_val(bnd), a1, a2, literal_pointer_val(args[3]),
3138+
literal_pointer_val((jl_value_t*)jl_module_call_func(ctx->module)),
3139+
ConstantInt::get(T_int32, (int)iskw) };
3140+
ctx->argDepth = last_depth;
3141+
return builder.CreateCall(prepare_call(jlmethod_func), ArrayRef<Value*>(&mdargs[0], 9));
3142+
}
31343143
}
31353144
else if (head == const_sym) {
31363145
jl_sym_t *sym = (jl_sym_t*)args[0];
@@ -5200,6 +5209,17 @@ static void init_julia_llvm_env(Module *m)
52005209
"jl_method_def", m);
52015210
add_named_global(jlmethod_func, (void*)&jl_method_def);
52025211

5212+
std::vector<Type*> funcdefargs(0);
5213+
funcdefargs.push_back(jl_pvalue_llvmt);
5214+
funcdefargs.push_back(jl_ppvalue_llvmt);
5215+
funcdefargs.push_back(jl_pvalue_llvmt);
5216+
funcdefargs.push_back(jl_pvalue_llvmt);
5217+
jlgenericfunction_func =
5218+
Function::Create(FunctionType::get(jl_pvalue_llvmt, funcdefargs, false),
5219+
Function::ExternalLinkage,
5220+
"jl_generic_function_def", m);
5221+
add_named_global(jlgenericfunction_func, (void*)&jl_generic_function_def);
5222+
52035223
std::vector<Type*> ehargs(0);
52045224
ehargs.push_back(T_pint8);
52055225
jlenter_func =

src/interpreter.c

+2
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,8 @@ static jl_value_t *eval(jl_value_t *e, jl_value_t **locals, size_t nl, size_t ng
294294
bp_owner = (jl_value_t*)jl_current_module;
295295
}
296296
}
297+
if (jl_expr_nargs(ex) == 1)
298+
return jl_generic_function_def(fname, bp, bp_owner, b);
297299
jl_value_t *atypes=NULL, *meth=NULL;
298300
JL_GC_PUSH2(&atypes, &meth);
299301
atypes = eval(args[1], locals, nl, ngensym);

src/julia-parser.scm

+27-22
Original file line numberDiff line numberDiff line change
@@ -1123,28 +1123,33 @@
11231123
((stagedfunction function macro)
11241124
(if (eq? word 'stagedfunction) (syntax-deprecation-warning s "stagedfunction" "@generated function"))
11251125
(let* ((paren (eqv? (require-token s) #\())
1126-
(sig (parse-call s))
1127-
(def (if (or (symbol? sig)
1128-
(and (pair? sig) (eq? (car sig) '|::|)
1129-
(symbol? (cadr sig))))
1130-
(if paren
1131-
;; in "function (x)" the (x) is a tuple
1132-
`(tuple ,sig)
1133-
;; function foo => syntax error
1134-
(error (string "expected \"(\" in \"" word "\" definition")))
1135-
(if (not (and (pair? sig)
1136-
(or (eq? (car sig) 'call)
1137-
(eq? (car sig) 'tuple))))
1138-
(error (string "expected \"(\" in \"" word "\" definition"))
1139-
sig)))
1140-
(loc (begin (if (not (eq? (peek-token s) 'end))
1141-
;; if ends on same line, don't skip the following newline
1142-
(skip-ws-and-comments (ts:port s)))
1143-
(line-number-filename-node s)))
1144-
(body (parse-block s)))
1145-
(expect-end s)
1146-
(add-filename-to-block! body loc)
1147-
(list word def body)))
1126+
(sig (parse-call s)))
1127+
(if (and (eq? word 'function) (not paren) (symbol? sig))
1128+
(begin (if (not (eq? (require-token s) 'end))
1129+
(error (string "expected \"end\" in definition of function \"" sig "\"")))
1130+
(take-token s)
1131+
`(function ,sig))
1132+
(let* ((def (if (or (symbol? sig)
1133+
(and (pair? sig) (eq? (car sig) '|::|)
1134+
(symbol? (cadr sig))))
1135+
(if paren
1136+
;; in "function (x)" the (x) is a tuple
1137+
`(tuple ,sig)
1138+
;; function foo => syntax error
1139+
(error (string "expected \"(\" in " word " definition")))
1140+
(if (not (and (pair? sig)
1141+
(or (eq? (car sig) 'call)
1142+
(eq? (car sig) 'tuple))))
1143+
(error (string "expected \"(\" in " word " definition"))
1144+
sig)))
1145+
(loc (begin (if (not (eq? (peek-token s) 'end))
1146+
;; if ends on same line, don't skip the following newline
1147+
(skip-ws-and-comments (ts:port s)))
1148+
(line-number-filename-node s)))
1149+
(body (parse-block s)))
1150+
(expect-end s)
1151+
(add-filename-to-block! body loc)
1152+
(list word def body)))))
11481153
((abstract)
11491154
(list 'abstract (parse-subtype-spec s)))
11501155
((type immutable)

src/julia-syntax.scm

+9-5
Original file line numberDiff line numberDiff line change
@@ -1086,7 +1086,9 @@
10861086
(expand-binding-forms
10871087
`(-> ,name ,(caddr e)))
10881088
e))
1089-
e)))
1089+
(if (and (length= e 2) (symbol? name))
1090+
`(method ,name)
1091+
e))))
10901092

10911093
((->)
10921094
(let ((a (cadr e))
@@ -3041,10 +3043,12 @@ So far only the second case can actually occur.
30413043
(vinfo:set-sa! vi #f)
30423044
(if (assq (car vi) captvars)
30433045
(vinfo:set-iasg! vi #t)))))
3044-
`(method ,(cadr e)
3045-
,(analyze-vars (caddr e) env captvars)
3046-
,(analyze-vars (cadddr e) env captvars)
3047-
,(caddddr e)))
3046+
(if (length= e 2)
3047+
`(method ,(cadr e))
3048+
`(method ,(cadr e)
3049+
,(analyze-vars (caddr e) env captvars)
3050+
,(analyze-vars (cadddr e) env captvars)
3051+
,(caddddr e))))
30483052
(else (cons (car e)
30493053
(map (lambda (x) (analyze-vars x env captvars))
30503054
(cdr e)))))))

src/julia.h

+2
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,8 @@ jl_expr_t *jl_exprn(jl_sym_t *head, size_t n);
966966
jl_function_t *jl_new_generic_function(jl_sym_t *name);
967967
void jl_add_method(jl_function_t *gf, jl_tupletype_t *types, jl_function_t *meth,
968968
jl_svec_t *tvars, int8_t isstaged);
969+
DLLEXPORT jl_value_t *jl_generic_function_def(jl_sym_t *name, jl_value_t **bp, jl_value_t *bp_owner,
970+
jl_binding_t *bnd);
969971
DLLEXPORT jl_value_t *jl_method_def(jl_sym_t *name, jl_value_t **bp, jl_value_t *bp_owner, jl_binding_t *bnd,
970972
jl_svec_t *argtypes, jl_function_t *f, jl_value_t *isstaged,
971973
jl_value_t *call_func, int iskw);

src/toplevel.c

+24
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,30 @@ static int type_contains(jl_value_t *ty, jl_value_t *x)
660660

661661
void print_func_loc(JL_STREAM *s, jl_lambda_info_t *li);
662662

663+
// empty generic function def
664+
// TODO: maybe have jl_method_def call this
665+
DLLEXPORT jl_value_t *jl_generic_function_def(jl_sym_t *name, jl_value_t **bp, jl_value_t *bp_owner,
666+
jl_binding_t *bnd)
667+
{
668+
jl_value_t *gf=NULL;
669+
670+
if (bnd && bnd->value != NULL && !bnd->constp)
671+
jl_errorf("cannot define function %s; it already has a value", bnd->name->name);
672+
if (*bp != NULL) {
673+
gf = *bp;
674+
if (!jl_is_gf(gf))
675+
jl_errorf("cannot define function %s; it already has a value", name->name);
676+
}
677+
if (bnd)
678+
bnd->constp = 1;
679+
if (*bp == NULL) {
680+
gf = (jl_value_t*)jl_new_generic_function(name);
681+
*bp = gf;
682+
if (bp_owner) gc_wb(bp_owner, gf);
683+
}
684+
return gf;
685+
}
686+
663687
DLLEXPORT jl_value_t *jl_method_def(jl_sym_t *name, jl_value_t **bp, jl_value_t *bp_owner,
664688
jl_binding_t *bnd,
665689
jl_svec_t *argdata, jl_function_t *f, jl_value_t *isstaged,

test/core.jl

+5
Original file line numberDiff line numberDiff line change
@@ -2899,3 +2899,8 @@ let t = Tuple{Type{Vector{Int}}}
28992899
t = Tuple{Type{Dict{TypeVar(:K, true)}}}
29002900
@test f11355(t) == 100
29012901
end
2902+
2903+
# issue #8283
2904+
function func8283 end
2905+
@test isa(func8283,Function) && isgeneric(func8283)
2906+
@test_throws MethodError func8283()

0 commit comments

Comments
 (0)