diff --git a/NEWS.md b/NEWS.md index 800a110efb642..bca7f09eb10fc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -24,6 +24,9 @@ New language features and macros in packages and user code ([#8791]). Type `?@doc` at the repl to see the current syntax and more information. + * Varargs functions may now declare the varargs length as `x...N` to + restrict dispatch. + Language changes ---------------- diff --git a/base/inference.jl b/base/inference.jl index f645054b88a75..87397ba2c73c2 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -1,3 +1,22 @@ +### Infer the types of variables in functions, given the types of the arguments + +# A few general notes: +# - The entry point to the inference code is typeinf_ext, which gets called from +# C and performs type inference on the AST and supplied argument types. +# - This works by "simulating" your code at the level of expressions. +# The abstract_eval_* series of functions computes the types of the objects that would +# be produced from eval-ing the corresponding individual expressions. +# - Inlining is performed during type inference; the main entry point is inlining_pass +# called from typeinf_uncached. +# - Julia's type system can be thought of in terms of sets. Any corresponds to the +# entire domain (any possible type), and Bottom corresponds to the empty set. +# Bottom is used to indicate that two types don't match, i.e., their intersection +# is the empty set; it also applies to functions that return nothing. +# Any is used when types can't be inferred, because potentially any type in the domain +# of all types might be returned. +# - During julia's bootstrap, type inference is initially off, but it gets turned on +# by the ccall at the end of this file. + # parameters limiting potentially-infinite types const MAX_TYPEUNION_LEN = 3 const MAX_TYPE_DEPTH = 4 @@ -103,6 +122,10 @@ isType(t::ANY) = isa(t,DataType) && is((t::DataType).name,Type.name) isvarargtype(t::ANY) = isa(t,DataType)&&is((t::DataType).name,Vararg.name) +## t_func is a cache of type information for Julia's builtins. Each entry +# is a 3-tuple (min_nargs, max_nargs, typefun), where typefun is a function +# that computes the return type from the input types. When named, these functions +# often have the pattern fname_tfunc, where fname is the corresponding function. const t_func = ObjectIdDict() #t_func[tuple] = (0, Inf, (args...)->limit_tuple_depth(args)) t_func[throw] = (1, 1, x->Bottom) @@ -188,7 +211,8 @@ const typeof_tfunc = function (t) Type{typeof(t)} end elseif isvarargtype(t) - Vararg{typeof_tfunc(t.parameters[1])} + length(t.parameters) == 1 ? Vararg{typeof_tfunc(t.parameters[1])} : + Vararg{typeof_tfunc(t.parameters[1]), t.parameters[2]} elseif isa(t,DataType) if isleaftype(t) Type{t} @@ -206,8 +230,10 @@ const typeof_tfunc = function (t) end end t_func[typeof] = (1, 1, typeof_tfunc) -# involving constants: typeassert, tupleref, getfield, fieldtype, apply_type -# therefore they get their arguments unevaluated + +# The following involve constants: typeassert, tupleref, getfield, fieldtype, apply_type +# For example, getfield(obj, i) can be inferred only if we know the value (not just type) +# of i. Therefore, these inference functions also receive their argument values (the variable A). t_func[typeassert] = (2, 2, (A, v, t)->(isType(t) ? typeintersect(v,t.parameters[1]) : isa(t,Tuple) && all(isType,t) ? @@ -523,6 +549,10 @@ function tuple_tfunc(argtypes::ANY, limit) return t end +# Perform type inference on a builtin function f for argument types argtypes +# For those builtins that cannot be inferred without knowing the values +# of the arguments (e.g., getfield(obj, i)), also pass the argument values +# in args. function builtin_tfunction(f::ANY, args::ANY, argtypes::ANY) isva = isvatuple(argtypes) if is(f,tuple) @@ -660,6 +690,8 @@ const limit_tuple_type_n = function (t::Tuple, lim::Int) return t end +# Return the instantiation of a method m, given the argument types tt. env contains +# method parameters, if any. let stagedcache=Dict{Any,Any}() global func_for_method function func_for_method(m::Method, tt, env) @@ -681,6 +713,8 @@ let stagedcache=Dict{Any,Any}() end end +# f is the function, fargs holds the argument values, argtypes is a tuple of argument types, +# and e is the expression that defines the function call function abstract_call_gf(f, fargs, argtypes, e) if length(argtypes)>1 && (argtypes[1] <: Tuple) && argtypes[2]===Int # allow tuple indexing functions to take advantage of constant @@ -802,6 +836,7 @@ function abstract_call_gf(f, fargs, argtypes, e) return rettype end +# f is the function, types is a tuple of types in the signature, and argtypes is a tuple of argument types for the call function invoke_tfunc(f, types, argtypes) argtypes = typeintersect(types,limit_tuple_type(argtypes)) if is(argtypes,Bottom) @@ -879,6 +914,10 @@ function abstract_apply(af, aargtypes, vtypes, sv, e) return abstract_call(af, (), Tuple, vtypes, sv, ()) end +# Main entry point for inference on a function-call +# f is the function, fargs holds the argument values, argtypes holds the argument types, +# vtypes is an ObjectIdDict of variables and their types, sv contains similar information as vtypes, +# and e is the expression that defines the function call. function abstract_call(f, fargs, argtypes, vtypes, sv::StaticVarInfo, e) if is(f,_apply) && length(fargs)>1 a2type = argtypes[2] @@ -959,6 +998,7 @@ function abstract_call(f, fargs, argtypes, vtypes, sv::StaticVarInfo, e) return rt end +# Abstract evaluation of an argument in an expression function abstract_eval_arg(a::ANY, vtypes::ANY, sv::StaticVarInfo) t = abstract_eval(a, vtypes, sv) if isa(t,TypeVar) && t.lb == Bottom && isleaftype(t.ub) @@ -967,6 +1007,7 @@ function abstract_eval_arg(a::ANY, vtypes::ANY, sv::StaticVarInfo) return t end +# Inference on a Expr(:call, args...) function abstract_eval_call(e, vtypes, sv::StaticVarInfo) fargs = e.args[2:end] argtypes = tuple([abstract_eval_arg(a, vtypes, sv) for a in fargs]...) @@ -995,6 +1036,8 @@ function abstract_eval_call(e, vtypes, sv::StaticVarInfo) return abstract_call(f, fargs, argtypes, vtypes, sv, e) end +# Main entry point for abstract evaluation of an expression +# e is the expression; vtypes is an ObjectIdDict of variables and their types function abstract_eval(e::ANY, vtypes, sv::StaticVarInfo) if isa(e,QuoteNode) return typeof((e::QuoteNode).value) @@ -1340,6 +1383,8 @@ f_argnames(ast) = is_rest_arg(arg::ANY) = (ccall(:jl_is_rest_arg,Int32,(Any,), arg) != 0) +# linfo is the "lambda info", atypes is a tuple containing the argument types, +# sparams is always empty, and def is described below function typeinf_ext(linfo, atypes::ANY, sparams::ANY, def) global inference_stack last = inference_stack diff --git a/doc/manual/functions.rst b/doc/manual/functions.rst index a433e3070b4ce..6a8e326492653 100644 --- a/doc/manual/functions.rst +++ b/doc/manual/functions.rst @@ -304,6 +304,8 @@ You can also return multiple values via an explicit usage of the This has the exact same effect as the previous definition of ``foo``. +.. _man-vararg-functions: + Varargs Functions ----------------- @@ -339,6 +341,8 @@ the zero or more values passed to ``bar`` after its first two arguments: In all these cases, ``x`` is bound to a tuple of the trailing values passed to ``bar``. +It is possible to constrain the number of values passed as a variable argument; this will be discussed later in :ref:`man-vararg-fixedlen`. + On the flip side, it is often handy to "splice" the values contained in an iterable collection into a function call as individual arguments. To do this, one also uses ``...`` but in the function call instead: diff --git a/doc/manual/methods.rst b/doc/manual/methods.rst index 6baf6b522e6ee..dcac90aa57c91 100644 --- a/doc/manual/methods.rst +++ b/doc/manual/methods.rst @@ -523,6 +523,32 @@ can also constrain type parameters of methods:: The ``same_type_numeric`` function behaves much like the ``same_type`` function defined above, but is only defined for pairs of numbers. +.. _man-vararg-fixedlen: + +Parametrically-constrained Varargs methods +------------------------------------------ + +Function parameters can also be used to constrain the number of arguments that may be supplied to a "varargs" function (:ref:`man-vararg-functions`). The notation ``...N`` is used to indicate such a constraint. For example: + +.. doctest:: + + julia> bar(a,b,x...2) = (a,b,x) + + julia> bar(1,2,3) + ERROR: MethodError: `bar` has no matching method bar(::Int, ::Int, ::Int) + + julia> bar(1,2,3,4) + (1,2,(3,4)) + + julia> bar(1,2,3,4,5) + ERROR: MethodError: `bar` has no method matching bar(::Int, ::Int, ::Int, ::Int, ::Int) + +More usefully, it is possible to constrain varargs methods by a parameter. For example:: + + function getindex{T,N}(A::AbstractArray{T,N}, indexes::Number...N) + +would be called only when the number of ``indexes`` matches the dimensionality of the array. + Note on Optional and keyword Arguments -------------------------------------- diff --git a/src/ast.c b/src/ast.c index 7b1c5f9cab06e..39d4a5938c634 100644 --- a/src/ast.c +++ b/src/ast.c @@ -752,7 +752,7 @@ int jl_is_rest_arg(jl_value_t *ex) if (((jl_expr_t*)ex)->head != colons_sym) return 0; jl_expr_t *atype = (jl_expr_t*)jl_exprarg(ex,1); if (!jl_is_expr(atype)) return 0; - if (atype->head != call_sym || jl_array_len(atype->args) != 3) + if (atype->head != call_sym || jl_array_len(atype->args) < 3 || jl_array_len(atype->args) > 4) return 0; if ((jl_sym_t*)jl_exprarg(atype,1) != dots_sym) return 0; diff --git a/src/builtins.c b/src/builtins.c index e4d52bcec88b1..06d57cc081ad4 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1319,6 +1319,7 @@ size_t jl_static_show_x(JL_STREAM *out, jl_value_t *v, int depth) else if (jl_is_vararg_type(v)) { n += jl_static_show_x(out, jl_tparam0(v), depth); n += jl_printf(out, "..."); + n += jl_static_show_x(out, jl_tparam1(v), depth); } else if (jl_is_datatype(v)) { jl_datatype_t *dv = (jl_datatype_t*)v; diff --git a/src/gf.c b/src/gf.c index 5d344f1815c03..ad18b71368653 100644 --- a/src/gf.c +++ b/src/gf.c @@ -738,57 +738,58 @@ static jl_function_t *cache_method(jl_methtable_t *mt, jl_tuple_t *type, // in general, here we want to find the biggest type that's not a // supertype of any other method signatures. so far we are conservative // and the types we find should be bigger. - if (!isstaged && jl_tuple_len(type) > mt->max_args && - jl_is_vararg_type(jl_tupleref(decl,jl_tuple_len(decl)-1))) { - size_t nspec = mt->max_args + 2; - limited = jl_alloc_tuple(nspec); - for(i=0; i < nspec-1; i++) { - jl_tupleset(limited, i, jl_tupleref(type, i)); - } - jl_value_t *lasttype = jl_tupleref(type,i-1); - // if all subsequent arguments are subtypes of lasttype, specialize - // on that instead of decl. for example, if decl is - // (Any...) - // and type is - // (Symbol, Symbol, Symbol) - // then specialize as (Symbol...), but if type is - // (Symbol, Int32, Expr) - // then specialize as (Any...) - size_t j = i; - int all_are_subtypes=1; - for(; j < jl_tuple_len(type); j++) { - if (!jl_subtype(jl_tupleref(type,j), lasttype, 0)) { - all_are_subtypes = 0; - break; + if (!isstaged && jl_tuple_len(type) > mt->max_args) { + jl_value_t *lastdeclt = jl_tupleref(decl,jl_tuple_len(decl)-1); + if (jl_is_vararg_type(lastdeclt) && !jl_is_vararg_fixedlen(lastdeclt)) { + size_t nspec = mt->max_args + 2; + limited = jl_alloc_tuple(nspec); + for(i=0; i < nspec-1; i++) { + jl_tupleset(limited, i, jl_tupleref(type, i)); } - } - type = limited; - if (all_are_subtypes) { - // avoid Type{Type{...}...}... - if (jl_is_type_type(lasttype) && jl_is_type_type(jl_tparam0(lasttype))) - lasttype = (jl_value_t*)jl_type_type; - temp = (jl_value_t*)jl_tuple1(lasttype); - jl_tupleset(type, i, jl_apply_type((jl_value_t*)jl_vararg_type, - (jl_tuple_t*)temp)); - } - else { - jl_value_t *lastdeclt = jl_tupleref(decl,jl_tuple_len(decl)-1); - if (jl_tuple_len(sparams) > 0) { - lastdeclt = (jl_value_t*) - jl_instantiate_type_with((jl_value_t*)lastdeclt, - sparams->data, - jl_tuple_len(sparams)/2); + jl_value_t *lasttype = jl_tupleref(type,i-1); + // if all subsequent arguments are subtypes of lasttype, specialize + // on that instead of decl. for example, if decl is + // (Any...) + // and type is + // (Symbol, Symbol, Symbol) + // then specialize as (Symbol...), but if type is + // (Symbol, Int32, Expr) + // then specialize as (Any...) + size_t j = i; + int all_are_subtypes=1; + for(; j < jl_tuple_len(type); j++) { + if (!jl_subtype(jl_tupleref(type,j), lasttype, 0)) { + all_are_subtypes = 0; + break; + } + } + type = limited; + if (all_are_subtypes) { + // avoid Type{Type{...}...}... + if (jl_is_type_type(lasttype) && jl_is_type_type(jl_tparam0(lasttype))) + lasttype = (jl_value_t*)jl_type_type; + temp = (jl_value_t*)jl_tuple1(lasttype); + jl_tupleset(type, i, jl_apply_type((jl_value_t*)jl_vararg_type, + (jl_tuple_t*)temp)); + } + else { + if (jl_tuple_len(sparams) > 0) { + lastdeclt = (jl_value_t*) + jl_instantiate_type_with((jl_value_t*)lastdeclt, + sparams->data, + jl_tuple_len(sparams)/2); + } + jl_tupleset(type, i, lastdeclt); } - jl_tupleset(type, i, lastdeclt); + // now there is a problem: the computed signature is more + // general than just the given arguments, so it might conflict + // with another definition that doesn't have cache instances yet. + // to fix this, we insert guard cache entries for all intersections + // of this signature and definitions. those guard entries will + // supersede this one in conflicted cases, alerting us that there + // should actually be a cache miss. + need_guard_entries = 1; } - // now there is a problem: the computed signature is more - // general than just the given arguments, so it might conflict - // with another definition that doesn't have cache instances yet. - // to fix this, we insert guard cache entries for all intersections - // of this signature and definitions. those guard entries will - // supersede this one in conflicted cases, alerting us that there - // should actually be a cache miss. - need_guard_entries = 1; } if (need_guard_entries) { @@ -924,6 +925,8 @@ static jl_function_t *cache_method(jl_methtable_t *mt, jl_tuple_t *type, return newmeth; } +// a holds the argument types, b the argument signature, tvars the parameters. +// On output, *penv holds (parameter1, value1, ...) pairs from intersection. static jl_value_t *lookup_match(jl_value_t *a, jl_value_t *b, jl_tuple_t **penv, jl_tuple_t *tvars) { diff --git a/src/jltypes.c b/src/jltypes.c index 372d05a8a5da7..60d978e6c23e7 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -431,8 +431,8 @@ static jl_value_t *intersect_tuple(jl_tuple_t *a, jl_tuple_t *b, jl_value_t *result = (jl_value_t*)tc; jl_value_t *ce = NULL; JL_GC_PUSH2(&tc, &ce); - size_t ai=0, bi=0, ci; - jl_value_t *ae=NULL, *be=NULL; + size_t ai=0, bi=0, ci, i; + jl_value_t *ae=NULL, *be=NULL, *bn=NULL; int aseq=0, bseq=0; for(ci=0; ci < n; ci++) { if (ai < al) { @@ -447,6 +447,7 @@ static jl_value_t *intersect_tuple(jl_tuple_t *a, jl_tuple_t *b, be = jl_tupleref(b,bi); if (jl_is_vararg_type(be)) { bseq=1; + bn = jl_tparam1(be); be = jl_tparam0(be); } bi++; @@ -471,6 +472,26 @@ static jl_value_t *intersect_tuple(jl_tuple_t *a, jl_tuple_t *b, } jl_tupleset(tc, ci, ce); } + // Check for a length-constrained vararg + if (bseq) { + if (!jl_is_long(bn)) { + // set bn from eqc parameters + for (i = 0; i < eqc->n; i+=2) + if (eqc->data[i] == bn) { + bn = eqc->data[i+1]; + break; + } + } + if (jl_is_long(bn)) { + long valen = jl_unbox_long(bn); + if (valen != (ai-bi+1)) + result = (jl_value_t*)jl_bottom_type; + } + else if (jl_is_typevar(bn) && ((jl_tvar_t*)bn)->bound) { + // set eqc parameter from valen, to support func{N}(x...N) + extend(bn, jl_box_long(ai-bi+1), eqc); + } + } done_intersect_tuple: JL_GC_POP(); return result; @@ -2140,14 +2161,26 @@ static int jl_subtype_le(jl_value_t *a, jl_value_t *b, int ta, int invariant); static int jl_tuple_subtype_(jl_value_t **child, size_t cl, jl_value_t **parent, size_t pl, int ta, int invariant) { - size_t ci=0, pi=0; + size_t ci=0, pi=0, pseqci=0; + int pseq=0; while (1) { + if (!pseq) + pseqci = ci; int cseq = !ta && (ci= cl) - return pi>=pl || (pseq && !invariant); + if (ci >= cl) { + if (pi >= pl) + return 1; + if (!(pseq && !invariant)) + return 0; + jl_value_t *lastarg = parent[pl-1]; + if (!jl_is_vararg_fixedlen(lastarg)) + return 1; + return (jl_is_long(jl_tparam1(lastarg)) && + ci-pseqci == jl_unbox_long(jl_tparam1(lastarg))); + } if (pi >= pl) return 0; jl_value_t *ce = child[ci]; @@ -2158,7 +2191,8 @@ static int jl_tuple_subtype_(jl_value_t **child, size_t cl, if (!jl_subtype_le(ce, pe, ta, invariant)) return 0; - if (cseq && pseq) return 1; + if (cseq && pseq) + return !jl_is_vararg_fixedlen(parent[pi]); if (!cseq) ci++; if (!pseq) pi++; } @@ -3081,7 +3115,8 @@ void jl_init_types(void) jl_type_type->parameters = jl_tuple(1, tttvar); jl_tuple_t *tv; - tv = jl_tuple1(tvar("T")); + //tv = jl_tuple1(tvar("T")); + tv = jl_tuple2(tvar("T"), tvar("N")); jl_vararg_type = jl_new_abstracttype((jl_value_t*)jl_symbol("Vararg"), jl_any_type, tv); diff --git a/src/julia-parser.scm b/src/julia-parser.scm index f1b3a9dbb4ba0..383092862e048 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -690,7 +690,13 @@ (loop (append ex (list argument)) #t))))) ((eq? t '...) (take-token s) - (list '... ex)) + (let ((tnxt (peek-token s))) + (if (and (or (number? tnxt) (symbol? tnxt)) (not (ts:space? s))) + (begin + (take-token s) + (set! t (list t tnxt)) + ))) + (list t ex)) (else ex))))) ; the principal non-terminals follow, in increasing precedence order @@ -775,6 +781,9 @@ (define (parse-comparison s) (let loop ((ex (parse-in s)) (first #t)) + ;; convert (('... N) var) into ('... var N) + (if (and (pair? ex) (pair? (car ex)) (eq? (car (car ex)) '...)) + (set! ex (list '... (cadr ex) (cadr (car ex))))) (let ((t (peek-token s))) (if (not (is-prec-comparison? t)) ex diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 5d3ed580b5804..1b236ae906146 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -110,7 +110,9 @@ (bad-formal-argument v)) (else (case (car v) - ((...) `(... ,(decl-type (cadr v)))) + ((...) (if (eq? (length v) 3) + `(... ,(decl-type (cadr v)) ,(caddr v)) + `(... ,(decl-type (cadr v))))) ((|::|) (if (not (symbol? (cadr v))) (bad-formal-argument (cadr v))) @@ -384,8 +386,12 @@ (cons (replace-end (expand-index-colon idx) a n tuples last) ret))))))) +(define (make-vararg t) (if (eq? (length t) 3) + `(curly Vararg ,(cadr t) ,(caddr t)) + `(curly Vararg ,(cadr t)))) + (define (make-decl n t) `(|::| ,n ,(if (and (pair? t) (eq? (car t) '...)) - `(curly Vararg ,(cadr t)) + (make-vararg t) t))) (define (function-expr argl body) @@ -401,9 +407,13 @@ ;; except for rest arg (define (method-lambda-expr argl body) (let ((argl (map (lambda (x) - (if (and (pair? x) (eq? (car x) '...)) + (if (vararg? x) (make-decl (arg-name x) (arg-type x)) - (arg-name x))) + (if (varargexpr? x) + (if (pair? (caddr x)) + x + `(|::| ,(arg-name x) (curly Vararg Any))) + (arg-name x)))) argl))) `(lambda ,argl (scope-block ,body)))) @@ -476,6 +486,15 @@ ,body ,isstaged)))))) (define (vararg? x) (and (pair? x) (eq? (car x) '...))) +(define (varargexpr? x) (and + (pair? x) + (eq? (car x) '::) + (or + (eq? (caddr x) 'Vararg) + (and + (pair? (caddr x)) + (length> (caddr x) 1) + (eq? (cadr (caddr x)) 'Vararg))))) (define (trans? x) (and (pair? x) (eq? (car x) '|.'|))) (define (ctrans? x) (and (pair? x) (eq? (car x) '|'|))) @@ -1809,7 +1828,7 @@ (error "assignment not allowed inside tuple")) (expand-forms (if (vararg? x) - `(curly Vararg ,(cadr x)) + (make-vararg x) x))) (cdr e)))) diff --git a/src/julia.h b/src/julia.h index 8d96e2ef99644..7b9928adeb927 100644 --- a/src/julia.h +++ b/src/julia.h @@ -845,6 +845,15 @@ DLLEXPORT ssize_t jl_unbox_gensym(jl_value_t *v); #define jl_long_type jl_int32_type #endif +STATIC_INLINE int jl_is_vararg_fixedlen(jl_value_t *v) +{ + assert(jl_is_vararg_type(v)); + jl_value_t *lenv = jl_tparam1(v); + if (jl_is_typevar(lenv)) + return ((jl_tvar_t*)lenv)->bound != 0; + return jl_is_long(lenv); +} + // structs DLLEXPORT int jl_field_index(jl_datatype_t *t, jl_sym_t *fld, int err); DLLEXPORT jl_value_t *jl_get_nth_field(jl_value_t *v, size_t i); diff --git a/test/core.jl b/test/core.jl index 52ab391f54230..2010f799f0a81 100644 --- a/test/core.jl +++ b/test/core.jl @@ -519,6 +519,45 @@ begin @test firstlast(Val{false}) == "Last" end +# x::Vararg{Any} declarations +begin + local f1, f2, f3 + f1(x...) = [x...] + f2(x::Vararg{Any}) = [x...] + f3(x::Vararg) = [x...] + @test f1(1,2,3) == [1,2,3] + @test f2(1,2,3) == [1,2,3] + @test f3(1,2,3) == [1,2,3] +end + +# dispatch with fixed-length varargs +begin + local gi, mysum, mysum2 + function gi{T,N}(A::AbstractArray{T,N}, indexes...N) + return true + end + @test_throws MethodError gi(zeros(2,2), 1) + @test gi(zeros(2,2), 1, 1) + @test_throws MethodError gi(zeros(2,2), 1, 1, 1) + @test gi(zeros(2,2,2), 1, 1, 1) + @test_throws MethodError gi(zeros(2,2,2), 1, 1) + function mysum{N}(x::Int...N) + s = 0 + for i = 1:N + s += x[i] + end + s + end + @test mysum(1,2,3) == 6 + @test mysum(1,2,3,4) == 10 + function mysum2(x::Int...3) + x[1] + x[2] + x[3] + end + @test_throws MethodError mysum2(1,2) + @test mysum2(1,2,3) == 6 + @test_throws MethodError mysum2(1,2,3,4) +end + # try/finally begin after = 0