From: Yegappan Lakshmanan Date: Mon, 21 Jul 2025 19:36:08 +0000 (+0200) Subject: patch 9.1.1577: Vim9: no generic support yet X-Git-Tag: v9.1.1577^0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3416cee36f1f668dd31f9ee8461be908d3d59c06;p=thirdparty%2Fvim.git patch 9.1.1577: Vim9: no generic support yet Problem: Vim9: no generic support yet Solution: Add support for generic functions, funcrefs and object/class methods (Yegappan Lakshmanan). closes: #17313 Signed-off-by: Yegappan Lakshmanan Signed-off-by: Christian Brabandt --- diff --git a/Filelist b/Filelist index 41eba31075..5153ded57d 100644 --- a/Filelist +++ b/Filelist @@ -177,6 +177,7 @@ SRC_ALL = \ src/vim9compile.c \ src/vim9execute.c \ src/vim9expr.c \ + src/vim9generics.c \ src/vim9instr.c \ src/vim9script.c \ src/vim9type.c \ @@ -362,6 +363,7 @@ SRC_ALL = \ src/proto/vim9compile.pro \ src/proto/vim9execute.pro \ src/proto/vim9expr.pro \ + src/proto/vim9generics.pro \ src/proto/vim9instr.pro \ src/proto/vim9script.pro \ src/proto/vim9type.pro \ diff --git a/runtime/doc/tags b/runtime/doc/tags index 5535036b2e..69811f9996 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -4615,6 +4615,9 @@ E1425 vim9class.txt /*E1425* E1426 vim9class.txt /*E1426* E1429 vim9class.txt /*E1429* E143 autocmd.txt /*E143* +E1432 vim9.txt /*E1432* +E1433 vim9.txt /*E1433* +E1434 vim9.txt /*E1434* E144 various.txt /*E144* E145 starting.txt /*E145* E146 change.txt /*E146* @@ -4674,7 +4677,17 @@ E1548 wayland.txt /*E1548* E1549 options.txt /*E1549* E155 sign.txt /*E155* E1550 options.txt /*E1550* +E1552 vim9.txt /*E1552* +E1553 vim9.txt /*E1553* +E1554 vim9.txt /*E1554* +E1555 vim9.txt /*E1555* +E1556 vim9.txt /*E1556* +E1557 vim9.txt /*E1557* +E1558 vim9.txt /*E1558* +E1559 vim9.txt /*E1559* E156 sign.txt /*E156* +E1560 vim9.txt /*E1560* +E1561 vim9.txt /*E1561* E157 sign.txt /*E157* E158 sign.txt /*E158* E159 sign.txt /*E159* @@ -7963,6 +7976,9 @@ gdb debug.txt /*gdb* gdb-version terminal.txt /*gdb-version* ge motion.txt /*ge* gender-neutral helphelp.txt /*gender-neutral* +generic-function-call vim9.txt /*generic-function-call* +generic-function-declaration vim9.txt /*generic-function-declaration* +generic-functions vim9.txt /*generic-functions* get() builtin.txt /*get()* get()-blob builtin.txt /*get()-blob* get()-dict builtin.txt /*get()-dict* @@ -11023,6 +11039,7 @@ type-casting vim9.txt /*type-casting* type-checking vim9.txt /*type-checking* type-inference vim9.txt /*type-inference* type-mistakes tips.txt /*type-mistakes* +type-variable-naming vim9.txt /*type-variable-naming* typealias vim9class.txt /*typealias* typename() builtin.txt /*typename()* typescript.vim syntax.txt /*typescript.vim* diff --git a/runtime/doc/todo.txt b/runtime/doc/todo.txt index 0a4c92fcb8..ff6b6a59ec 100644 --- a/runtime/doc/todo.txt +++ b/runtime/doc/todo.txt @@ -1,4 +1,4 @@ -*todo.txt* For Vim version 9.1. Last change: 2025 Jun 12 +*todo.txt* For Vim version 9.1. Last change: 2025 Jul 21 VIM REFERENCE MANUAL by Bram Moolenaar @@ -115,7 +115,6 @@ Further Vim9 improvements: - For chaining, allow using the class name as type for function return value. - Implement "specifies" interface - - Implement generics - Add "assignable" (class or child)? - More efficient way for interface member index than iterating over list? - a variant of type() that returns a different type for each class? diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt index 79aec13adc..10b47424fa 100644 --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -41551,6 +41551,8 @@ Add support for internal builtin functions with vim9 objects, see Enum support for Vim9 script |:enum| +Generic function support for Vim9 script |generic-functions| + Support for protected _new() method Support for compiling all the methods in a Vim9 class using |:defcompile|. diff --git a/runtime/doc/vim9.txt b/runtime/doc/vim9.txt index 0dae57debc..063c179364 100644 --- a/runtime/doc/vim9.txt +++ b/runtime/doc/vim9.txt @@ -1,4 +1,4 @@ -*vim9.txt* For Vim version 9.1. Last change: 2025 Apr 27 +*vim9.txt* For Vim version 9.1. Last change: 2025 Jul 21 VIM REFERENCE MANUAL by Bram Moolenaar @@ -15,10 +15,11 @@ features in Vim9 script. 2. Differences |vim9-differences| 3. New style functions |fast-functions| 4. Types |vim9-types| -5. Namespace, Import and Export |vim9script| -6. Classes and interfaces |vim9-classes| +5. Generic functions |generic-functions| +6. Namespace, Import and Export |vim9script| +7. Classes and interfaces |vim9-classes| -9. Rationale |vim9-rationale| +8. Rationale |vim9-rationale| ============================================================================== @@ -1895,7 +1896,146 @@ corresponding empty value. ============================================================================== -5. Namespace, Import and Export + *generic-functions* +5. Generic functions + +A generic function allows using the same function with different type +arguments, while retaining type checking for arguments and the return value. +This provides type safety and code reusability. + +Declaration~ + *generic-function-declaration* + *E1553* *E1554* *E1559* +The type parameters for a generic function are declared in angle brackets "<" +and ">" directly after the function name. Multiple type names are separated +by commas: > + + def[!] {funcname}<{type} [, {types}]>([arguments])[: {return-type}] + {function body} + enddef +< +These type parameters can then be used like any other type within the function +signature and body. Example: > + + def MyFunc(param1: T): T + var f: A + var x = param1 + return x + enddef +< + *type-variable-naming* *E1552* +The convention is to use a single uppercase letter for a type variable (e.g., +T, A, X), although longer names are allowed. The name must start with an +uppercase letter. + + *E1558* *E1560* +A function must be declared and used either as generic or as a regular +function - but not both. + + *E1561* +A type variable name must not conflict with other defined names, such as class +names, type aliases, enum names, function names or other type variable names. + +Calling a generic function~ + *generic-function-call* +To call a generic function, specify the concrete types in "<" and ">" +between the function name and the argument list: > + + MyFunc>() +< + *E1555* *E1556* *E1557* +The number of concrete types provided when calling a generic function must +match the number of type variables in the function. An empty type list is not +allowed. Any Vim9 type (|vim9-types|) can be used as a concrete type in a +generic function. + +Spaces are not allowed between the function name and "<", or between ">" and +the opening "(". + +A generic function can be exported and imported like a regular function. +See |:export| and |:import|. + +A generic function can be defined inside another regular or generic function. + +Referencing type variables in generic types~ + +Instead of concrete types, type variables can be used with generic types. +This is useful for complex data structures like lists of dictionaries or +dictionaries of lists. Example: > + + vim9script + + def Flatten(x: list>): list + var result: list = [] + for inner in x + result += inner + endfor + return result + enddef + + echo Flatten([[1, 2], [3]]) +< + +Generic class method~ + +A Vim9 class method can be a generic function: > + + class A + def Foo() + enddef + endclass + var a = A.new() + a.Foo() +< + *E1432* *E1433* *E1434* +A generic class method in a base class can be overridden by a generic method +in a child class. The number of type variables must match between both +methods. A concrete class method cannot be overridden by a generic method, +and vice versa. + +Generic function reference~ + +A function reference (|Funcref|) can be a generic function. This allows for +creating factories of functions that operate on specific types: > + + vim9script + + def MakeEcho(): func(T): T + return (x: T): T => x + enddef + + var EchoNumber = MakeEcho() + echo EchoNumber(123) + + var EchoString = MakeEcho() + echo EchoString('abc') +< +Compiling and Disassembling Generic functions~ + +The |:defcompile| command can be used to compile a generic function with a +specific list of concrete types: > + + defcompile MyFunc, dict> +< +The |:disassemble| command can be used to list the instructions generated for +a generic function: > + + disassemble MyFunc> + disassemble MyFunc> +< +Limitations and Future Work~ + +Currently, Vim does not support: + - Type inference for type variables: All types must be explicitly specified + when calling a generic function. + - Type constraints: It's not possible to restrict a type variable to a + specific class or interface (e.g., `T extends SomeInterface`). + - Default type arguments: Providing a default type for a type parameter + when not explicitly specified. + +============================================================================== + +6. Namespace, Import and Export *vim9script* *vim9-export* *vim9-import* A Vim9 script can be written to be imported. This means that some items are @@ -2174,7 +2314,7 @@ Or: > ============================================================================== -6. Classes and interfaces *vim9-classes* +7. Classes and interfaces *vim9-classes* In legacy script a Dictionary could be used as a kind-of object, by adding members that are functions. However, this is quite inefficient and requires @@ -2188,7 +2328,7 @@ functionality it is located in a separate help file: |vim9class.txt|. ============================================================================== -9. Rationale *vim9-rationale* +8. Rationale *vim9-rationale* The :def command ~ diff --git a/src/Make_ami.mak b/src/Make_ami.mak index bd8b525a83..7c96467357 100644 --- a/src/Make_ami.mak +++ b/src/Make_ami.mak @@ -183,6 +183,7 @@ SRC += \ vim9compile.c \ vim9execute.c \ vim9expr.c \ + vim9generics.c \ vim9instr.c \ vim9script.c \ vim9type.c \ diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak index 37f9f6c407..6ae1c0a054 100644 --- a/src/Make_cyg_ming.mak +++ b/src/Make_cyg_ming.mak @@ -895,6 +895,7 @@ OBJ = \ $(OUTDIR)/vim9compile.o \ $(OUTDIR)/vim9execute.o \ $(OUTDIR)/vim9expr.o \ + $(OUTDIR)/vim9generics.o \ $(OUTDIR)/vim9instr.o \ $(OUTDIR)/vim9script.o \ $(OUTDIR)/vim9type.o \ @@ -1328,6 +1329,8 @@ $(OUTDIR)/vim9execute.o: vim9execute.c $(INCL) vim9.h $(OUTDIR)/vim9expr.o: vim9expr.c $(INCL) vim9.h +$(OUTDIR)/vim9generics.o: vim9generics.c $(INCL) vim9.h + $(OUTDIR)/vim9instr.o: vim9instr.c $(INCL) vim9.h $(OUTDIR)/vim9script.o: vim9script.c $(INCL) vim9.h diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak index 64b033178d..f4899d5661 100644 --- a/src/Make_mvc.mak +++ b/src/Make_mvc.mak @@ -803,6 +803,7 @@ OBJ = \ $(OUTDIR)\vim9compile.obj \ $(OUTDIR)\vim9execute.obj \ $(OUTDIR)\vim9expr.obj \ + $(OUTDIR)\vim9generics.obj \ $(OUTDIR)\vim9instr.obj \ $(OUTDIR)\vim9script.obj \ $(OUTDIR)\vim9type.obj \ @@ -1824,6 +1825,8 @@ $(OUTDIR)/vim9execute.obj: $(OUTDIR) vim9execute.c $(INCL) vim9.h $(OUTDIR)/vim9expr.obj: $(OUTDIR) vim9expr.c $(INCL) vim9.h +$(OUTDIR)/vim9generics.obj: $(OUTDIR) vim9generics.c $(INCL) vim9.h + $(OUTDIR)/vim9instr.obj: $(OUTDIR) vim9instr.c $(INCL) vim9.h $(OUTDIR)/vim9script.obj: $(OUTDIR) vim9script.c $(INCL) vim9.h @@ -2028,6 +2031,7 @@ proto.h: \ proto/vim9compile.pro \ proto/vim9execute.pro \ proto/vim9expr.pro \ + proto/vim9generics.pro \ proto/vim9instr.pro \ proto/vim9script.pro \ proto/vim9type.pro \ diff --git a/src/Make_vms.mms b/src/Make_vms.mms index 9dce2b5ed2..fb247f3ba4 100644 --- a/src/Make_vms.mms +++ b/src/Make_vms.mms @@ -446,6 +446,7 @@ SRC = \ vim9compile.c \ vim9execute.c \ vim9expr.c \ + vim9generics.c \ vim9instr.c \ vim9script.c \ vim9type.c \ @@ -582,6 +583,7 @@ OBJ = \ vim9compile.obj \ vim9execute.obj \ vim9expr.obj \ + vim9generics.obj \ vim9instr.obj \ vim9script.obj \ vim9type.obj \ @@ -1225,6 +1227,10 @@ vim9expr.obj : vim9expr.c vim.h [.auto]config.h feature.h os_unix.h \ ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \ gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ errors.h globals.h version.h +vim9generics.obj : vim9generics.c vim.h [.auto]config.h feature.h os_unix.h \ + ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \ + gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ + errors.h globals.h version.h vim9instr.obj : vim9instr.c vim.h [.auto]config.h feature.h os_unix.h \ ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \ gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ diff --git a/src/Makefile b/src/Makefile index 175f659d6e..e3a8600422 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1598,6 +1598,7 @@ BASIC_SRC = \ vim9compile.c \ vim9execute.c \ vim9expr.c \ + vim9generics.c \ vim9instr.c \ vim9script.c \ vim9type.c \ @@ -1771,6 +1772,7 @@ OBJ_COMMON = \ objects/vim9compile.o \ objects/vim9execute.o \ objects/vim9expr.o \ + objects/vim9generics.o \ objects/vim9instr.o \ objects/vim9script.o \ objects/vim9type.o \ @@ -1967,6 +1969,7 @@ PRO_AUTO = \ vim9compile.pro \ vim9execute.pro \ vim9expr.pro \ + vim9generics.pro \ vim9instr.pro \ vim9script.pro \ vim9type.pro \ @@ -3630,6 +3633,9 @@ objects/vim9execute.o: vim9execute.c objects/vim9expr.o: vim9expr.c $(CCC) -o $@ vim9expr.c +objects/vim9generics.o: vim9generics.c + $(CCC) -o $@ vim9generics.c + objects/vim9instr.o: vim9instr.c $(CCC) -o $@ vim9instr.c @@ -4357,6 +4363,11 @@ objects/vim9expr.o: vim9expr.c vim.h protodef.h auto/config.h feature.h os_unix. proto/gui_beval.pro structs.h regexp.h gui.h libvterm/include/vterm.h \ libvterm/include/vterm_keycodes.h alloc.h ex_cmds.h spell.h proto.h \ globals.h errors.h vim9.h +objects/vim9generics.o: vim9generics.c vim.h protodef.h auto/config.h feature.h \ + os_unix.h auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h \ + beval.h proto/gui_beval.pro structs.h regexp.h gui.h \ + libvterm/include/vterm.h libvterm/include/vterm_keycodes.h alloc.h \ + ex_cmds.h spell.h proto.h globals.h errors.h vim9.h objects/vim9instr.o: vim9instr.c vim.h protodef.h auto/config.h feature.h \ os_unix.h auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h \ beval.h proto/gui_beval.pro structs.h regexp.h gui.h \ diff --git a/src/errors.h b/src/errors.h index 2a1e11ea67..16a0fb3ed0 100644 --- a/src/errors.h +++ b/src/errors.h @@ -3624,8 +3624,14 @@ EXTERN char e_uninitialized_object_var_reference[] INIT(= N_("E1430: Uninitialized object variable '%s' referenced")); EXTERN char e_abstract_method_str_direct[] INIT(= N_("E1431: Abstract method \"%s\" in class \"%s\" cannot be accessed directly")); -#endif -// E1432 - E1499 unused (reserved for Vim9 class support) +EXTERN char e_generic_method_str_override_with_concrete_method_in_class_str[] + INIT(= N_("E1432: Overriding generic method \"%s\" in class \"%s\" with a concrete method")); +EXTERN char e_concrete_method_str_override_with_generic_method_in_class_str[] + INIT(= N_("E1433: Overriding concrete method \"%s\" in class \"%s\" with a generic method")); +EXTERN char e_generic_method_str_type_arguments_mismatch_in_class_str[] + INIT(= N_("E1434: Mismatched number of type variables for generic method \"%s\" in class \"%s\"")); +#endif +// E1435 - E1499 unused (reserved for Vim9 class support) EXTERN char e_cannot_mix_positional_and_non_positional_str[] INIT(= N_("E1500: Cannot mix positional and non-positional arguments: %s")); EXTERN char e_fmt_arg_nr_unused_str[] @@ -3746,3 +3752,25 @@ EXTERN char e_failed_to_find_all_diff_anchors[] EXTERN char e_cannot_open_a_popup_window_to_a_closing_buffer[] INIT(= N_("E1551: Cannot open a popup window to a closing buffer")); #endif +#ifdef FEAT_EVAL +EXTERN char e_type_var_name_must_start_with_uppercase_letter_str[] + INIT(= N_("E1552: Type variable name must start with an uppercase letter: %s")); +EXTERN char e_missing_comma_in_generic_function_str[] + INIT(= N_("E1553: Missing comma after type in generic function: %s")); +EXTERN char e_missing_closing_angle_bracket_in_generic_function_str[] + INIT(= N_("E1554: Missing '>' in generic function: %s")); +EXTERN char e_empty_type_list_for_generic_function_str[] + INIT(= N_("E1555: Empty type list specified for generic function '%s'")); +EXTERN char e_too_many_types_for_generic_function_str[] + INIT(= N_("E1556: Too many types specified for generic function '%s'")); +EXTERN char e_not_enough_types_for_generic_function_str[] + INIT(= N_("E1557: Not enough types specified for generic function '%s'")); +EXTERN char e_unknown_generic_function_str[] + INIT(= N_("E1558: Unknown generic function: %s")); +EXTERN char e_generic_func_missing_type_args_str[] + INIT(= N_("E1559: Type arguments missing for generic function '%s'")); +EXTERN char e_not_a_generic_function_str[] + INIT(= N_("E1560: Not a generic function: %s")); +EXTERN char e_duplicate_type_var_name_str[] + INIT(= N_("E1561: Duplicate type variable name: %s")); +#endif diff --git a/src/eval.c b/src/eval.c index b93bc4201d..604b471d19 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1620,12 +1620,12 @@ get_lval_tuple( * The method index, method function pointer and method type are returned in * "lp". */ - static void + static int get_lval_oc_method( lval_T *lp, class_T *cl, char_u *key, - char_u *key_end, + char_u **key_end, vartype_T v_type) { // Look for a method with this name. @@ -1637,8 +1637,13 @@ get_lval_oc_method( ufunc_T *fp; fp = method_lookup(cl, round == 1 ? VAR_CLASS : VAR_OBJECT, - key, key_end - key, &m_idx); + key, *key_end - key, &m_idx); lp->ll_oi = m_idx; + + // process generic method (if present) + if (fp && (fp = eval_generic_func(fp, key, key_end)) == NULL) + return FAIL; + if (fp != NULL) { lp->ll_ufunc = fp; @@ -1646,6 +1651,8 @@ get_lval_oc_method( break; } } + + return OK; } /* @@ -1711,7 +1718,7 @@ get_lval_oc_variable( get_lval_class_or_obj( lval_T *lp, char_u *key, - char_u *key_end, + char_u **key_end, vartype_T v_type, class_T *cl_exec, int flags, @@ -1747,19 +1754,20 @@ get_lval_class_or_obj( lp->ll_valtype = NULL; if (flags & GLV_PREFER_FUNC) - get_lval_oc_method(lp, cl, key, key_end, v_type); + if (get_lval_oc_method(lp, cl, key, key_end, v_type) == FAIL) + return FAIL; // Look for object/class member variable if (lp->ll_valtype == NULL) { - if (get_lval_oc_variable(lp, cl, key, key_end, v_type, cl_exec, flags) + if (get_lval_oc_variable(lp, cl, key, *key_end, v_type, cl_exec, flags) == FAIL) return FAIL; } if (lp->ll_valtype == NULL) { - member_not_found_msg(cl, v_type, key, key_end - key); + member_not_found_msg(cl, v_type, key, *key_end - key); return FAIL; } @@ -2039,7 +2047,7 @@ get_lval_subscript( } else // v_type == VAR_CLASS || v_type == VAR_OBJECT { - if (get_lval_class_or_obj(lp, key, p, v_type, cl_exec, flags, + if (get_lval_class_or_obj(lp, key, &p, v_type, cl_exec, flags, quiet) == FAIL) goto done; } @@ -2213,7 +2221,7 @@ get_lval( // parse the type after the name lp->ll_type = parse_type(&tp, &SCRIPT_ITEM(current_sctx.sc_sid)->sn_type_list, - !quiet); + NULL, NULL, !quiet); if (lp->ll_type == NULL && !quiet) return NULL; lp->ll_name_end = tp; @@ -3179,6 +3187,8 @@ eval_func( funcexe.fe_basetv = basetv; funcexe.fe_check_type = type; funcexe.fe_found_var = found_var; + if (evalarg != NULL) + funcexe.fe_cctx = evalarg->eval_cctx; ret = get_func_tv(s, len, rettv, arg, evalarg, &funcexe); } vim_free(s); @@ -4725,7 +4735,7 @@ eval8( { ++*arg; ga_init2(&type_list, sizeof(type_T *), 10); - want_type = parse_type(arg, &type_list, TRUE); + want_type = parse_type(arg, &type_list, NULL, NULL, TRUE); if (want_type == NULL && (evaluate || **arg != '>')) { clear_type_list(&type_list); @@ -4973,7 +4983,7 @@ eval9_nested_expr( if (vim9script) { - ret = get_lambda_tv(arg, rettv, TRUE, evalarg); + ret = get_lambda_tv(arg, rettv, TRUE, evalarg, NULL); if (ret == OK && evaluate) { ufunc_T *ufunc = rettv->vval.v_partial->pt_func; @@ -5062,7 +5072,8 @@ eval9_var_func_name( semsg(_(e_cannot_use_s_colon_in_vim9_script_str), s); ret = FAIL; } - else if ((vim9script ? **arg : *skipwhite(*arg)) == '(') + else if ((vim9script ? **arg : *skipwhite(*arg)) == '(' + || (vim9script && generic_func_call(arg))) { // "name(..." recursive! *arg = skipwhite(*arg); @@ -5079,6 +5090,12 @@ eval9_var_func_name( *name_start = s; ret = eval_variable(s, len, 0, rettv, NULL, EVAL_VAR_VERBOSE + EVAL_VAR_IMPORT); + + // skip the generic function arguments (if present) + // they are already processed by eval_variable + if (ret == OK && vim9script && **arg == '<' + && rettv->v_type == VAR_FUNC) + ret = skip_generic_func_type_args(arg); } } else @@ -5230,7 +5247,7 @@ eval9( case '{': if (vim9script) ret = NOTDONE; else - ret = get_lambda_tv(arg, rettv, vim9script, evalarg); + ret = get_lambda_tv(arg, rettv, vim9script, evalarg, NULL); if (ret == NOTDONE) ret = eval_dict(arg, rettv, evalarg, FALSE); break; @@ -5438,6 +5455,8 @@ call_func_rettv( funcexe.fe_partial = pt; funcexe.fe_selfdict = selfdict; funcexe.fe_basetv = basetv; + if (evalarg != NULL) + funcexe.fe_cctx = evalarg->eval_cctx; ret = get_func_tv(s, -1, rettv, arg, evalarg, &funcexe); theend: @@ -5471,7 +5490,7 @@ eval_lambda( if (**arg == '{') { // ->{lambda}() - ret = get_lambda_tv(arg, rettv, FALSE, evalarg); + ret = get_lambda_tv(arg, rettv, FALSE, evalarg, NULL); } else { @@ -5663,6 +5682,15 @@ eval_index( ; if (keylen == 0) return FAIL; + if (vim9script && key[keylen] == '<') + { + // skip generic type arguments + char_u *p = &key[keylen]; + + if (skip_generic_func_type_args(&p) == FAIL) + return FAIL; + keylen = p - key; + } *arg = key + keylen; } else @@ -7374,7 +7402,19 @@ handle_subscript( else { rettv->v_type = VAR_FUNC; - rettv->vval.v_string = vim_strnsave(ufunc->uf_name, ufunc->uf_namelen); + if (**arg == '<') + { + char_u *s = get_generic_func_name(ufunc, arg); + if (s != NULL) + rettv->vval.v_string = s; + else + ret = FAIL; + } + else + { + rettv->vval.v_string = + vim_strnsave(ufunc->uf_name, ufunc->uf_namelen); + } } continue; } diff --git a/src/evalfunc.c b/src/evalfunc.c index 3c8339ee37..825df5fbfd 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -3949,6 +3949,17 @@ f_call(typval_T *argvars, typval_T *rettv) emsg_funcname(e_unknown_function_str, func); return; } + if (*p == '<') + { + // generic function + char_u *s = append_generic_func_type_args(tofree, STRLEN(tofree), + &p); + if (s != NULL) + { + vim_free(tofree); + tofree = s; + } + } func = tofree; } @@ -5166,6 +5177,7 @@ common_function(typval_T *argvars, typval_T *rettv, int is_funcref) partial_T *arg_pt = NULL; char_u *trans_name = NULL; int is_global = FALSE; + char_u *start_bracket = NULL; if (in_vim9script() && (check_for_string_or_func_arg(argvars, 0) == FAIL @@ -5203,6 +5215,12 @@ common_function(typval_T *argvars, typval_T *rettv, int is_funcref) name = s; trans_name = save_function_name(&name, &is_global, FALSE, TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL); + if (*name == '<') + { + // generic function + start_bracket = name; + skip_generic_func_type_args(&name); + } if (*name != NUL) s = NULL; } @@ -5345,6 +5363,9 @@ common_function(typval_T *argvars, typval_T *rettv, int is_funcref) else if (is_funcref) { pt->pt_func = find_func(trans_name, is_global); + if (IS_GENERIC_FUNC(pt->pt_func) && start_bracket != NULL) + pt->pt_func = eval_generic_func(pt->pt_func, s, + &start_bracket); func_ptr_ref(pt->pt_func); vim_free(name); } @@ -5367,8 +5388,19 @@ common_function(typval_T *argvars, typval_T *rettv, int is_funcref) { // result is a VAR_FUNC rettv->v_type = VAR_FUNC; - rettv->vval.v_string = name; - func_ref(name); + if (start_bracket == NULL) + { + rettv->vval.v_string = name; + func_ref(name); + } + else + { + // generic function + STRCPY(IObuff, name); + STRCAT(IObuff, start_bracket); + rettv->vval.v_string = vim_strsave(IObuff); + vim_free(name); + } } } theend: diff --git a/src/evalvars.c b/src/evalvars.c index d94d3e8608..37e2f373aa 100644 --- a/src/evalvars.c +++ b/src/evalvars.c @@ -3196,6 +3196,20 @@ eval_variable( int has_g_prefix = STRNCMP(name, "g:", 2) == 0; ufunc_T *ufunc = find_func(name, FALSE); + if (ufunc != NULL && cc == '<') + { + // handle generic function + char_u *argp = name + len; + name[len] = cc; + ufunc = eval_generic_func(ufunc, name, &argp); + name[len] = NUL; + if (ufunc == NULL) + { + ret = FAIL; + goto done; + } + } + // In Vim9 script we can get a function reference by using the // function name. For a global non-autoload function "g:" is // required. @@ -3207,9 +3221,22 @@ eval_variable( { rettv->v_type = VAR_FUNC; if (has_g_prefix) + { // Keep the "g:", otherwise script-local may be // assumed. - rettv->vval.v_string = vim_strsave(name); + if (cc != '<') + rettv->vval.v_string = vim_strsave(name); + else + { + // append the generic function arguments + char_u *argp = name + len; + name[len] = cc; + rettv->vval.v_string = + append_generic_func_type_args(name, len, + &argp); + name[len] = NUL; + } + } else rettv->vval.v_string = vim_strnsave(ufunc->uf_name, ufunc->uf_namelen); if (rettv->vval.v_string != NULL) diff --git a/src/ex_docmd.c b/src/ex_docmd.c index 06f8f8b795..d1465202a2 100644 --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -3715,6 +3715,16 @@ find_ex_command( // "&option" can be followed by "->" or "=", check below } + if (*p == '<' && vim9) + { + // generic function + if (skip_generic_func_type_args(&p) == FAIL) + { + eap->cmdidx = CMD_SIZE; + return p; + } + } + swp = skipwhite(p); if ( diff --git a/src/po/vim.pot b/src/po/vim.pot index 88a2081c47..ca9cbdd180 100644 --- a/src/po/vim.pot +++ b/src/po/vim.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-07-20 16:31+0200\n" +"POT-Creation-Date: 2025-07-21 21:33+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -8553,6 +8553,24 @@ msgid "" "E1431: Abstract method \"%s\" in class \"%s\" cannot be accessed directly" msgstr "" +#, c-format +msgid "" +"E1432: Overriding generic method \"%s\" in class \"%s\" with a concrete " +"method" +msgstr "" + +#, c-format +msgid "" +"E1433: Overriding concrete method \"%s\" in class \"%s\" with a generic " +"method" +msgstr "" + +#, c-format +msgid "" +"E1434: Mismatched number of type variables for generic method \"%s\" in " +"class \"%s\"" +msgstr "" + #, c-format msgid "E1500: Cannot mix positional and non-positional arguments: %s" msgstr "" @@ -8733,6 +8751,46 @@ msgstr "" msgid "E1551: Cannot open a popup window to a closing buffer" msgstr "" +#, c-format +msgid "E1552: Type variable name must start with an uppercase letter: %s" +msgstr "" + +#, c-format +msgid "E1553: Missing comma after type in generic function: %s" +msgstr "" + +#, c-format +msgid "E1554: Missing '>' in generic function: %s" +msgstr "" + +#, c-format +msgid "E1555: Empty type list specified for generic function '%s'" +msgstr "" + +#, c-format +msgid "E1556: Too many types specified for generic function '%s'" +msgstr "" + +#, c-format +msgid "E1557: Not enough types specified for generic function '%s'" +msgstr "" + +#, c-format +msgid "E1558: Unknown generic function: %s" +msgstr "" + +#, c-format +msgid "E1559: Type arguments missing for generic function '%s'" +msgstr "" + +#, c-format +msgid "E1560: Not a generic function: %s" +msgstr "" + +#, c-format +msgid "E1561: Duplicate type variable name: %s" +msgstr "" + #. type of cmdline window or 0 #. result of cmdline window or 0 #. buffer of cmdline window or NULL diff --git a/src/proto.h b/src/proto.h index 28cb809d40..8c345b90f0 100644 --- a/src/proto.h +++ b/src/proto.h @@ -230,6 +230,7 @@ void mbyte_im_set_active(int active_arg); # include "vim9compile.pro" # include "vim9execute.pro" # include "vim9expr.pro" +# include "vim9generics.pro" # include "vim9instr.pro" # include "vim9type.pro" # endif diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro index e91dd3a491..da5a90f774 100644 --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -4,7 +4,7 @@ hashtab_T *func_tbl_get(void); char_u *make_ufunc_name_readable(char_u *name, char_u *buf, size_t bufsize); string_T get_lambda_name(void); char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state); -int get_lambda_tv(char_u **arg, typval_T *rettv, int types_optional, evalarg_T *evalarg); +int get_lambda_tv(char_u **arg, typval_T *rettv, int types_optional, evalarg_T *evalarg, cctx_T *cctx); char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, type_T **type, int no_autoload, int new_function, int *found_var); void emsg_funcname(char *ermsg, char_u *name); int get_func_arguments(char_u **arg, evalarg_T *evalarg, int partial_argc, typval_T *argvars, int *argcount, int is_builtin); @@ -46,7 +46,7 @@ char_u *get_scriptlocal_funcname(char_u *funcname); char_u *alloc_printable_func_name(char_u *fname); char_u *save_function_name(char_u **name, int *is_global, int skip, int flags, funcdict_T *fudi); void list_functions(regmatch_T *regmatch); -ufunc_T *define_function(exarg_T *eap, char_u *name_arg, garray_T *lines_to_free, int class_flags, ocmember_T *obj_members, int obj_member_count); +ufunc_T *define_function(exarg_T *eap, char_u *name_arg, garray_T *lines_to_free, int class_flags, ocmember_T *obj_members, int obj_member_count, cctx_T *cctx); void ex_function(exarg_T *eap); int get_func_arity(char_u *name, int *required, int *optional, int *varargs); ufunc_T *find_func_by_name(char_u *name, compiletype_T *compile_type); @@ -58,7 +58,7 @@ int has_varargs(ufunc_T *ufunc); int function_exists(char_u *name, int no_deref); char_u *get_expanded_name(char_u *name, int check); char_u *get_user_func_name(expand_T *xp, int idx); -ufunc_T *copy_function(ufunc_T *fp); +ufunc_T *copy_function(ufunc_T *fp, int extra_namelen); void ex_delfunction(exarg_T *eap); void func_unref(char_u *name); void func_ptr_unref(ufunc_T *fp); diff --git a/src/proto/vim9expr.pro b/src/proto/vim9expr.pro index d28908c400..58c79fc027 100644 --- a/src/proto/vim9expr.pro +++ b/src/proto/vim9expr.pro @@ -3,7 +3,7 @@ int generate_ppconst(cctx_T *cctx, ppconst_T *ppconst); void clear_ppconst(ppconst_T *ppconst); int compile_member(int is_slice, int *keeping_dict, cctx_T *cctx); int compile_load_scriptvar(cctx_T *cctx, char_u *name, char_u *start, char_u **end, imported_T *import); -int compile_load(char_u **arg, char_u *end_arg, cctx_T *cctx, int is_expr, int error); +int compile_load(char_u **arg, size_t namelen, char_u *end_arg, cctx_T *cctx, int is_expr, int error); int compile_arguments(char_u **arg, cctx_T *cctx, int *argcount, ca_special_T special_fn); char_u *to_name_end(char_u *arg, int use_namespace); char_u *to_name_const_end(char_u *arg); diff --git a/src/proto/vim9generics.pro b/src/proto/vim9generics.pro new file mode 100644 index 0000000000..2fcae4db75 --- /dev/null +++ b/src/proto/vim9generics.pro @@ -0,0 +1,19 @@ +/* vim9generics.c */ +char_u *generic_func_find_open_bracket(char_u *name); +int skip_generic_func_type_args(char_u **argp); +char_u *append_generic_func_type_args(char_u *funcname, size_t namelen, char_u **argp); +char_u *get_generic_func_name(ufunc_T *fp, char_u **arg); +char_u *parse_generic_func_type_args(char_u *func_name, size_t namelen, char_u *start, gfargs_tab_T *gfatab, cctx_T *cctx); +char_u *parse_generic_func_type_params(char_u *func_name, char_u *p, gfargs_tab_T *gfatab, cctx_T *cctx); +void generic_func_init(ufunc_T *fp, gfargs_tab_T *gfatab); +void generic_func_args_table_init(gfargs_tab_T *gfatab); +int generic_func_args_table_size(gfargs_tab_T *gfatab); +void generic_func_args_table_clear(gfargs_tab_T *gfatab); +void copy_generic_function(ufunc_T *fp, ufunc_T *new_fp); +ufunc_T *eval_generic_func(ufunc_T *ufunc, char_u *name, char_u **arg); +int generic_func_call(char_u **arg); +ufunc_T *generic_func_get(ufunc_T *fp, gfargs_tab_T *gfatab); +ufunc_T *find_generic_func(ufunc_T *ufunc, char_u *name, char_u **arg); +type_T *find_generic_type(char_u *type_name, size_t namelen, ufunc_T *ufunc, cctx_T *cctx); +void generic_func_clear_items(ufunc_T *fp); +/* vim: set ft=c : */ diff --git a/src/proto/vim9type.pro b/src/proto/vim9type.pro index 865a93c1b9..c32da26ab6 100644 --- a/src/proto/vim9type.pro +++ b/src/proto/vim9type.pro @@ -1,6 +1,7 @@ /* vim9type.c */ type_T *get_type_ptr(garray_T *type_gap); type_T *copy_type(type_T *type, garray_T *type_gap); +type_T *copy_type_deep(type_T *type, garray_T *type_gap); void clear_type_list(garray_T *gap); void clear_func_type_list(garray_T *gap, type_T **func_type); type_T *alloc_type(type_T *type); @@ -25,7 +26,7 @@ int check_type(type_T *expected, type_T *actual, int give_msg, where_T where); int check_type_maybe(type_T *expected, type_T *actual, int give_msg, where_T where); int check_argument_types(type_T *type, typval_T *argvars, int argcount, typval_T *base_tv, char_u *name); char_u *skip_type(char_u *start, int optional); -type_T *parse_type(char_u **arg, garray_T *type_gap, int give_error); +type_T *parse_type(char_u **arg, garray_T *type_gap, ufunc_T *ufunc, cctx_T *cctx, int give_error); int equal_type(type_T *type1, type_T *type2, int flags); void common_type(type_T *type1, type_T *type2, type_T **dest, garray_T *type_gap); type_T *get_item_type(type_T *type); diff --git a/src/structs.h b/src/structs.h index e3c69ebe3b..725ec395bd 100644 --- a/src/structs.h +++ b/src/structs.h @@ -74,6 +74,8 @@ typedef struct dictvar_S dict_T; typedef struct partial_S partial_T; typedef struct blobvar_S blob_T; typedef struct tuplevar_S tuple_T; +typedef struct generictype_S generic_T; +typedef struct gfargs_tab_S gfargs_tab_T; typedef struct window_S win_T; typedef struct wininfo_S wininfo_T; @@ -1543,6 +1545,10 @@ typedef struct { #define TTFLAG_STATIC 0x10 // one of the static types, e.g. t_any #define TTFLAG_CONST 0x20 // cannot be changed #define TTFLAG_SUPER 0x40 // object from "super". +#define TTFLAG_GENERIC 0x80 // generic type + +#define IS_GENERIC_TYPE(type) \ + ((type->tt_flags & TTFLAG_GENERIC) == TTFLAG_GENERIC) typedef enum { VIM_ACCESS_PRIVATE, // read/write only inside the class @@ -1840,6 +1846,25 @@ struct tuplevar_S char tv_lock; // zero, VAR_LOCKED, VAR_FIXED }; +/* + * Structure to hold a generic type information + */ +struct generictype_S +{ + type_T *gt_type; // generic or concrete type + char_u *gt_name; // type name +}; + +/* + * Generic function args table + */ +struct gfargs_tab_S +{ + garray_T gfat_args; + garray_T gfat_param_types; + garray_T gfat_arg_types; +}; + typedef int (*cfunc_T)(int argcount, typval_T *argvars, typval_T *rettv, void *state); typedef void (*cfunc_free_T)(void *state); @@ -1926,6 +1951,13 @@ struct ufunc_S void *uf_cb_state; // state of uf_cb # endif + // for generic functions + int uf_generic_argcount;// type argument count + generic_T *uf_generic_args; // generic types + type_T *uf_generic_param_types; // list of allocated generic types + garray_T uf_generic_arg_types; // list of allocated type arguments + hashtab_T uf_generic_functab; // generic function table + garray_T uf_lines; // function lines int uf_debug_tick; // when last checked for a breakpoint in this @@ -1987,6 +2019,7 @@ struct ufunc_S #define FC_OBJECT 0x4000 // object method #define FC_NEW 0x8000 // constructor #define FC_ABSTRACT 0x10000 // abstract method +#define FC_GENERIC 0x20000 // generic function // Is "ufunc" an object method? #define IS_OBJECT_METHOD(ufunc) ((ufunc->uf_flags & FC_OBJECT) == FC_OBJECT) @@ -1994,6 +2027,7 @@ struct ufunc_S #define IS_CONSTRUCTOR_METHOD(ufunc) ((ufunc->uf_flags & FC_NEW) == FC_NEW) // Is "ufunc" an abstract class method? #define IS_ABSTRACT_METHOD(ufunc) ((ufunc->uf_flags & FC_ABSTRACT) == FC_ABSTRACT) +#define IS_GENERIC_FUNC(ufunc) (((ufunc)->uf_flags & FC_GENERIC) == FC_GENERIC) #define MAX_FUNC_ARGS 20 // maximum number of function arguments #define VAR_SHORT_LEN 20 // short variable name length @@ -2323,6 +2357,7 @@ typedef struct { type_T *fe_check_type; // type from funcref or NULL int fe_found_var; // if the function is not found then give an // error that a variable is not callable. + cctx_T *fe_cctx; // when compiling a :def function } funcexe_T; /* diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak index e61bf2b50e..0d4aeb0432 100644 --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -48,6 +48,7 @@ TEST_VIM9 = \ test_vim9_expr \ test_vim9_fails \ test_vim9_func \ + test_vim9_generics \ test_vim9_import \ test_vim9_python3 \ test_vim9_script \ @@ -63,6 +64,7 @@ TEST_VIM9_RES = \ test_vim9_expr.res \ test_vim9_fails.res \ test_vim9_func.res \ + test_vim9_generics.res \ test_vim9_import.res \ test_vim9_python3.res \ test_vim9_script.res \ diff --git a/src/testdir/test_vim9_generics.vim b/src/testdir/test_vim9_generics.vim new file mode 100644 index 0000000000..2cf93eede9 --- /dev/null +++ b/src/testdir/test_vim9_generics.vim @@ -0,0 +1,3548 @@ +" Test Vim9 generic function + +import './util/vim9.vim' as v9 + +" Test for definint a generic function +def Test_generic_func_definition() + var lines =<< trim END + vim9script + def Fn(x: A, y: B): A + return x + enddef + defcompile + END + v9.CheckSourceSuccess(lines) + + lines =<< trim END + vim9script + def Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1552: Type variable name must start with an uppercase letter: t>()', 2) + + lines =<< trim END + vim9script + def Fn<>() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1555: Empty type list specified for generic function', 2) + + lines =<< trim END + vim9script + def Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1008: Missing after >()', 2) + + lines =<< trim END + vim9script + def Fn<,>() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1008: Missing after <,>(', 2) + + lines =<< trim END + vim9script + def Fn after ()', 2) + + # Use a multi-character generic type name + lines =<< trim END + vim9script + def Fn(a: MyType1, b: My_Type2): My_Type2 + var x: MyType1 + var y: My_Type2 + return b + enddef + assert_equal([1, 2], Fn>('a', [1, 2])) + END + v9.CheckSourceSuccess(lines) + + # Use a generic type name starting with a lower case letter + lines =<< trim END + vim9script + def Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1552: Type variable name must start with an uppercase letter: mytype>()', 2) + + # Use a non-alphanumeric character in the generic type name + lines =<< trim END + vim9script + def Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1553: Missing comma after type in generic function: My-type>()', 2) + + # Use an existing type name as the generic type name + lines =<< trim END + vim9script + type FooBar = number + def Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1041: Redefining script item: "FooBar"', 3) + + # Use a very long type name + lines =<< trim END + vim9script + def Fn() + enddef + defcompile + END + v9.CheckSourceSuccess(lines) + + # Use a function name as the generic type name + lines =<< trim END + vim9script + def MyFunc() + enddef + def Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1041: Redefining script item: "MyFunc"', 4) +enddef + +" Test for white space error when defining a generic function +def Test_generic_func_definition_whitespace_error() + var lines =<< trim END + vim9script + def Fn () + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1068: No white space allowed before '<': ()", 2) + + lines =<< trim END + vim9script + def Fn () + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1068: No white space allowed before '(': (", 2) + + lines =<< trim END + vim9script + def Fn< A>() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1202: No white space allowed after '<': < A>()", 2) + + lines =<< trim END + vim9script + def Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1202: No white space allowed after 'A': A >()", 2) + + lines =<< trim END + vim9script + def Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1069: White space required after ',': ,>()", 2) + + lines =<< trim END + vim9script + def Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1008: Missing after >()", 2) + + lines =<< trim END + vim9script + def Fn<, A>() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1008: Missing after <, A>()", 2) + + lines =<< trim END + vim9script + def Fn<,A>() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1008: Missing after <,A>()", 2) + + lines =<< trim END + vim9script + def Fn< , A>() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1202: No white space allowed after '<': < , A>()", 2) + + lines =<< trim END + vim9script + def Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1069: White space required after ',': ,B>(", 2) + + lines =<< trim END + vim9script + def Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1202: No white space allowed after 'A': A , B>()", 2) + + lines =<< trim END + vim9script + def Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1202: No white space allowed after 'B': B >()", 2) + + lines =<< trim END + vim9script + def Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1202: No white space allowed after 'MyType': MyType , FooBar>()", 2) +enddef + +" Test for invoking a generic function +def Test_generic_func_invoke() + var lines =<< trim END + vim9script + def Fn() + enddef + Fn() + END + v9.CheckSourceFailure(lines, 'E1556: Too many types specified for generic function', 4) + + lines =<< trim END + vim9script + def Fn() + enddef + Fn() + END + v9.CheckSourceFailure(lines, 'E1557: Not enough types specified for generic function', 4) + + lines =<< trim END + vim9script + def Fn() + enddef + Fn<>() + END + v9.CheckSourceFailure(lines, "E1555: Empty type list specified for generic function '<>()'", 4) + + lines =<< trim END + vim9script + def Fn() + enddef + Fn<> + END + v9.CheckSourceFailure(lines, "E1555: Empty type list specified for generic function '<>'", 4) + + lines =<< trim END + vim9script + def Fn() + enddef + Fn() + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 4) + + lines =<< trim END + vim9script + def Fn() + enddef + Fn< + END + v9.CheckSourceFailure(lines, "E1554: Missing '>' in generic function: <", 4) + + lines =<< trim END + vim9script + def Fn() + enddef + Fn<() + END + v9.CheckSourceFailure(lines, "E1008: Missing after <", 4) + + lines =<< trim END + vim9script + def Fn() + enddef + Fn + END + v9.CheckSourceFailure(lines, 'E492: Not an editor command: Fn', 4) + + lines =<< trim END + vim9script + def Fn() + enddef + Fn( + END + v9.CheckSourceFailure(lines, 'E116: Invalid arguments for function Fn(', 4) + + lines =<< trim END + vim9script + def Fn() + enddef + Fn () + END + v9.CheckSourceFailure(lines, 'E492: Not an editor command: Fn ()', 4) + + lines =<< trim END + vim9script + def Fn() + enddef + Fn() + END + v9.CheckSourceFailure(lines, 'E1008: Missing after ()', 4) + + lines =<< trim END + vim9script + def Fn() + enddef + Fn() + END + v9.CheckSourceFailure(lines, 'E1010: Type not recognized: abc', 4) + + lines =<< trim END + vim9script + def Fn() + enddef + Fn() + END + v9.CheckSourceFailure(lines, "E1202: No white space allowed after 'number': () + xxx + enddef + Fn() + END + v9.CheckSourceFailure(lines, 'E476: Invalid command: xxx', 1) +enddef + +" Test for whitespace error when invoking a generic function +def Test_generic_func_invoke_whitespace_error() + var lines =<< trim END + vim9script + def Fn() + enddef + Fn< number>() + END + v9.CheckSourceFailure(lines, "E1202: No white space allowed after '<': < number>()", 4) + + lines =<< trim END + vim9script + def Fn() + enddef + Fn() + END + v9.CheckSourceFailure(lines, "E1202: No white space allowed after 'number': () + enddef + Fn() + END + v9.CheckSourceFailure(lines, "E1069: White space required after ',': ()", 4) + + lines =<< trim END + vim9script + def Fn() + enddef + Fn() + END + v9.CheckSourceFailure(lines, "E1008: Missing after ()", 4) + + lines =<< trim END + vim9script + def Fn() + enddef + Fn() + END + v9.CheckSourceFailure(lines, "E1069: White space required after ',': ()", 4) +enddef + +def Test_generic_func_typename() + var lines =<< trim END + vim9script + + def Foo(a: list, b: dict): list + return [] + enddef + + def Fn(x: T, s: string) + assert_equal(s, typename(x)) + enddef + + Fn(true, 'bool') + Fn(10, 'number') + Fn(3.4, 'float') + Fn('abc', 'string') + Fn(0z1020, 'blob') + Fn>>([[0z10, 0z20], [0z30]], 'list>') + Fn>((1, 'abc'), 'tuple') + Fn>({a: 'a', b: 'b'}, 'dict') + Fn(test_null_job(), 'job') + Fn(test_null_channel(), 'channel') + Fn(function('Foo'), 'func(list, dict): list') + END + v9.CheckSourceSuccess(lines) +enddef + +def Test_generic_func_single_arg() + var lines =<< trim END + vim9script + + def Fn(x: A): number + return len(x) + enddef + + assert_equal(3, Fn>([1, 2, 3])) + assert_equal(2, Fn>({a: 1, b: 2})) + assert_equal(1, Fn(0z10)) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for using a generic type as the type of a function argument +def Test_generic_func_arg_type() + var lines =<< trim END + vim9script + + def F1(x: list): list + return x + enddef + + def F2(y: dict): dict + return y + enddef + + assert_equal(['a', 'b'], F1(['a', 'b'])) + assert_equal({a: 0z10, b: 0z20}, F2({a: 0z10, b: 0z20})) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for using a tuple type for a generic function argument +def Test_generic_func_tuple_arg_type() + var lines =<< trim END + vim9script + + def Fn(x: tuple): tuple + return x + enddef + assert_equal((1, 2), Fn((1, 2))) + assert_equal(('a', 'b'), Fn(('a', 'b'))) + END + v9.CheckSourceSuccess(lines) + + lines =<< trim END + vim9script + + def Fn(x: tuple>): tuple> + return x + enddef + assert_equal(('a', 1, 2), Fn(('a', 1, 2))) + assert_equal((3, 'a', 'b'), Fn((3, 'a', 'b'))) + END + v9.CheckSourceSuccess(lines) +enddef + +def Test_generic_func_ret_type() + var lines =<< trim END + vim9script + + def Fn(x: A): A + return x + enddef + + assert_equal([1], Fn>([1])) + assert_equal({a: 1}, Fn>({a: 1})) + assert_equal((1,), Fn>((1,))) + assert_equal(0z10, Fn(0z10)) + END + v9.CheckSourceSuccess(lines) + + # Using the generic type as the member of the List return value + lines =<< trim END + vim9script + + def Fn(x: A): list + return [x] + enddef + + assert_equal([1], Fn(1)) + assert_equal(['abc'], Fn('abc')) + END + v9.CheckSourceSuccess(lines) + + # Using the generic type as the member of the Dict return value + lines =<< trim END + vim9script + + def Fn(x: A): dict + return {v: x} + enddef + + assert_equal({v: 1}, Fn(1)) + assert_equal({v: 'abc'}, Fn('abc')) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for using a generic type as the type of the vararg variable +def Test_generic_func_varargs() + var lines =<< trim END + vim9script + + def Fn(...x: list>): list> + return x + enddef + + assert_equal([[1], [2], [3]], Fn([1], [2], [3])) + assert_equal([['a'], ['b'], ['c']], Fn(['a'], ['b'], ['c'])) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for using func type as a generic function argument type +def Test_generic_func_type_as_argument() + var lines =<< trim END + vim9script + + def Fn(Foo: func(A, B): C): string + return typename(Foo) + enddef + + def F1(a: number, b: string): blob + return 0z10 + enddef + + def F2(a: float, b: blob): string + return 'abc' + enddef + + assert_equal('func(number, string): blob', Fn(F1)) + assert_equal('func(float, blob): string', Fn(F2)) + END + v9.CheckSourceSuccess(lines) +enddef + +def Test_generic_nested_call() + var lines =<< trim END + vim9script + + def Fn(n: number, x: A): A + if n + return x + endif + + assert_equal('abc', Fn(1, 'abc')) + + return x + enddef + + assert_equal(10, Fn(0, 10)) + END + v9.CheckSourceSuccess(lines) +enddef + +def Test_generic_failure_in_def_function() + var lines =<< trim END + vim9script + + def Fn() + enddef + + def Foo() + Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1010: Type not recognized: abc', 1) + + lines =<< trim END + vim9script + + def Foo() + Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1010: Type not recognized: abc', 1) + + lines =<< trim END + vim9script + + def Fn() + enddef + + def Foo() + Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1556: Too many types specified for generic function', 1) + + lines =<< trim END + vim9script + + def Fn() + enddef + + def Foo() + Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1557: Not enough types specified for generic function', 1) + + lines =<< trim END + vim9script + + def Fn() + enddef + + def Foo() + Fn<>() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1555: Empty type list specified for generic function '<>()'", 1) + + lines =<< trim END + vim9script + + def Fn(x: A, y: B) + enddef + + def Foo() + Fn(10, 'abc') + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 1) + + lines =<< trim END + vim9script + + def Fn(x: number) + enddef + + def Foo() + Fn(10) + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1560: Not a generic function', 1) + + lines =<< trim END + vim9script + + def Fn(x: A, y: B) + enddef + + def Foo() + Fn(10) + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E119: Not enough arguments for function', 1) + + lines =<< trim END + vim9script + + def Fn() + enddef + + def Foo() + Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1008: Missing after ()', 1) + + lines =<< trim END + vim9script + + def Fn() + enddef + + def Foo() + Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1010: Type not recognized: abc', 1) + + lines =<< trim END + vim9script + + def Fn() + enddef + + def Foo() + Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1202: No white space allowed after 'number'", 1) +enddef + +" Test for using function() to get a generic funcref +def Test_get_generic_funcref_using_function() + var lines =<< trim END + vim9script + def Fn(x: A): A + return x + enddef + var Fx = function(Fn>) + assert_equal([1], Fx([1])) + END + v9.CheckSourceSuccess(lines) + + # Get a generic funcref without specifying any type arguments + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function(Fn) + Fx() + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 5) + + # Get a generic funcref specifying additional type arguments + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function(Fn) + END + v9.CheckSourceFailure(lines, 'E1556: Too many types specified for generic function', 4) + + # Get a generic funcref specifying less type arguments + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function(Fn) + END + v9.CheckSourceFailure(lines, 'E1557: Not enough types specified for generic function', 4) + + # Get a generic funcref specifying non-existing type + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function(Fn) + END + v9.CheckSourceFailure(lines, 'E1010: Type not recognized: foobar', 4) + + # Get a generic funcref specifying an empty type argument list + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function(Fn<>) + END + v9.CheckSourceFailure(lines, "E1555: Empty type list specified for generic function '<>)'", 4) + + # Get a generic funcref specifying only the opening bracket after name + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function(Fn<) + END + v9.CheckSourceFailure(lines, 'E1008: Missing after <', 4) + + # Get a generic funcref specifying only the opening bracket and type + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function(Fn() + enddef + var Fx = function(Fn) + END + v9.CheckSourceFailure(lines, 'E1560: Not a generic function: Fn', 4) + + # Call a generic funcref using a different argument type + lines =<< trim END + vim9script + def Fn(t: T) + enddef + var Fx = function(Fn) + Fx(10) + END + v9.CheckSourceFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number', 5) + + # Assign a generic funcref return value to a variable of different type + lines =<< trim END + vim9script + def Fn(t: T): T + return t + enddef + var Fx = function(Fn) + var x: number = Fx('abc') + END + v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected number but got string', 6) + + # Call a generic funcref specifying types + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function(Fn) + Fx() + END + v9.CheckSourceFailure(lines, 'E15: Invalid expression: "Fx()"', 5) +enddef + +def Test_generic_funcref_string() + var lines =<< trim END + vim9script + + def Fn(x: A): A + return x + enddef + + var Fx = function('Fn>') + assert_equal([1], Fx([1])) + END + v9.CheckSourceSuccess(lines) + + # Get a generic funcref without specifying any type arguments + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function('Fn') + Fx() + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 5) + + # Get a generic funcref specifying additional type arguments + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function('Fn') + END + v9.CheckSourceFailure(lines, 'E1556: Too many types specified for generic function', 4) + + # Get a generic funcref specifying less type arguments + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function('Fn') + END + v9.CheckSourceFailure(lines, 'E1557: Not enough types specified for generic function', 4) + + # Get a generic funcref specifying non-existing type + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function('Fn') + END + v9.CheckSourceFailure(lines, 'E1010: Type not recognized: foobar', 4) + + # Get a generic funcref specifying an empty type argument list + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function('Fn<>') + END + v9.CheckSourceFailure(lines, 'E1555: Empty type list specified for generic function', 4) + + # Get a generic funcref specifying only the opening bracket after name + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function('Fn<') + END + v9.CheckSourceFailure(lines, "E1554: Missing '>' in generic function: <", 4) + + # Get a generic funcref specifying only the opening bracket and type + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function('Fn' in generic function: () + enddef + var Fx = function('Fn' in generic function: ') + Fx() + END + v9.CheckSourceFailure(lines, 'E1560: Not a generic function:', 5) + + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function('Fn') + Fx() + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 5) + + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function('Fn') + Fx<>() + END + v9.CheckSourceFailure(lines, "E1555: Empty type list specified for generic function '<>()'", 5) + + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function('Fn') + Fx() + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 5) + + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function('Fn') + Fx() + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 5) + + lines =<< trim END + vim9script + def Fn() + enddef + var Fx = function('Fn') + Fx() + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 5) +enddef + +" Test for calling a generic funcref from another function +def Test_generic_funcref_string_from_another_function() + var lines =<< trim END + vim9script + + def Fn(x: A): A + return x + enddef + + def Foo() + var Fx = function('Fn>') + assert_equal([1], Fx([1])) + enddef + Foo() + END + v9.CheckSourceSuccess(lines) + + # Get a generic funcref without specifying any type arguments + lines =<< trim END + vim9script + def Fn() + enddef + def Foo() + var Fx = function('Fn') + Fx() + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 2) + + # Get a generic funcref specifying additional type arguments + lines =<< trim END + vim9script + def Fn() + enddef + def Foo() + var Fx = function('Fn') + Fx() + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1556: Too many types specified for generic function', 2) + + # Get a generic funcref specifying less type arguments + lines =<< trim END + vim9script + def Fn() + enddef + def Foo() + var Fx = function('Fn') + Fx() + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1557: Not enough types specified for generic function', 2) + + # Get a generic funcref specifying an empty type argument list + lines =<< trim END + vim9script + def Fn() + enddef + def Foo() + var Fx = function('Fn<>') + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1555: Empty type list specified for generic function', 1) + + # Get a generic funcref specifying only the opening bracket after name + lines =<< trim END + vim9script + def Fn() + enddef + def Foo() + var Fx = function('Fn<') + enddef + Foo() + END + v9.CheckSourceFailure(lines, "E1554: Missing '>' in generic function: <", 1) + + # Get a generic funcref specifying only the opening bracket and type + lines =<< trim END + vim9script + def Fn() + enddef + def Foo() + var Fx = function('Fn' in generic function: () + enddef + def Foo() + var Fx = function('Fn' in generic function: ') + Fx() + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1560: Not a generic function:', 2) + + lines =<< trim END + vim9script + def Fn(x: A): A + return x + enddef + def Foo() + var Fx = function('Fn') + Fx() + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 2) + + lines =<< trim END + vim9script + def Fn() + enddef + def Foo() + var Fx = function('Fn') + Fx<>() + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1555: Empty type list specified for generic function', 2) + + lines =<< trim END + vim9script + def Fn() + enddef + def Foo() + var Fx = function('Fn') + Fx() + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 2) + + lines =<< trim END + vim9script + def Fn() + enddef + def Foo() + var Fx = function('Fn') + Fx() + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 2) +enddef + +def Test_generic_obj_method() + var lines =<< trim END + vim9script + + class A + def Fn(t: X): X + var n: X = t + return n + enddef + endclass + + var a = A.new() + assert_equal(['a', 'b'], a.Fn>(['a', 'b'])) + END + v9.CheckSourceSuccess(lines) + + lines =<< trim END + vim9script + + class A + def Fn<>() + enddef + endclass + defcompile + END + v9.CheckSourceFailure(lines, "E1555: Empty type list specified for generic function 'Fn'", 4) + + lines =<< trim END + vim9script + + class A + def Fn<,>() + enddef + endclass + defcompile + END + v9.CheckSourceFailure(lines, "E1008: Missing after <,>()", 4) + + lines =<< trim END + vim9script + + class A + def Fn() + enddef + endclass + var a = A.new() + a.Fn<>() + END + v9.CheckSourceFailureList(lines, ["E1555: Empty type list specified for generic function '<>()'"]) + + lines =<< trim END + vim9script + + class A + def Fn() + enddef + endclass + var a = A.new() + a.Fn() + END + v9.CheckSourceFailure(lines, "E1556: Too many types specified for generic function 'Fn'", 8) + + lines =<< trim END + vim9script + + class A + def Fn() + enddef + endclass + var a = A.new() + a.Fn() + END + v9.CheckSourceFailure(lines, "E1557: Not enough types specified for generic function 'Fn'", 8) + + lines =<< trim END + vim9script + + class A + def Fn() + enddef + endclass + var a = A.new() + a.Fn() + END + v9.CheckSourceFailure(lines, "E1559: Type arguments missing for generic function", 8) + + lines =<< trim END + vim9script + + class A + def Fn() + enddef + endclass + var a = A.new() + a.Fn() + END + v9.CheckSourceFailure(lines, 'E1560: Not a generic function: Fn', 8) +enddef + +def Test_generic_obj_method_call_from_another_method() + var lines =<< trim END + vim9script + + class A + def Fn(t: X): X + var n: X = t + return n + enddef + endclass + + def Foo() + var a = A.new() + assert_equal(['a', 'b'], a.Fn>(['a', 'b'])) + enddef + Foo() + END + v9.CheckSourceSuccess(lines) + + lines =<< trim END + vim9script + + class A + def Fn() + enddef + endclass + + def Foo() + var a = A.new() + a.Fn<>() + enddef + defcompile + END + v9.CheckSourceFailureList(lines, ["E1555: Empty type list specified for generic function '<>()'"]) + + lines =<< trim END + vim9script + + class A + def Fn() + enddef + endclass + + def Foo() + var a = A.new() + a.Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1556: Too many types specified for generic function 'Fn'", 2) + + lines =<< trim END + vim9script + + class A + def Fn() + enddef + endclass + + def Foo() + var a = A.new() + a.Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1557: Not enough types specified for generic function 'Fn'", 2) + + lines =<< trim END + vim9script + + class A + def Fn() + enddef + endclass + + def Foo() + var a = A.new() + a.Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1559: Type arguments missing for generic function", 2) + + lines =<< trim END + vim9script + + class A + def Fn() + enddef + endclass + + def Foo() + var a = A.new() + a.Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1560: Not a generic function: Fn', 2) + + # Try calling a non-existing generic object method + lines =<< trim END + vim9script + + class A + endclass + + def Foo() + var a = A.new() + a.Bar() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1325: Method "Bar" not found in class "A"', 2) + + # Error in compiling generic object method arguments + lines =<< trim END + vim9script + + class A + def Fn(x: number, y: number) + enddef + endclass + + def Foo() + var a = A.new() + a.Fn(10,) + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1069: White space required after ',': ,)", 2) + + # Try calling a super abstract method from a child class + lines =<< trim END + vim9script + + abstract class A + abstract def F1() + endclass + + class B extends A + def F1() + enddef + def Foo() + super.F1() + enddef + endclass + defcompile + END + v9.CheckSourceFailure(lines, 'E1431: Abstract method "F1" in class "A" cannot be accessed directly', 1) + + # Try calling a protected method in a class + lines =<< trim END + vim9script + + class A + def _Foo() + enddef + endclass + + def Bar() + var a = A.new() + a._Foo() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1366: Cannot access protected method: _Foo()', 2) +enddef + +def Test_generic_class_method() + var lines =<< trim END + vim9script + + class A + static def Fn(t: X): X + var n: X = t + return n + enddef + endclass + + assert_equal(['a', 'b'], A.Fn>(['a', 'b'])) + END + v9.CheckSourceSuccess(lines) + + lines =<< trim END + vim9script + + class A + static def Fn<>() + enddef + endclass + defcompile + END + v9.CheckSourceFailure(lines, "E1555: Empty type list specified for generic function 'Fn'", 4) + + lines =<< trim END + vim9script + + class A + static def Fn<,>() + enddef + endclass + defcompile + END + v9.CheckSourceFailure(lines, "E1008: Missing after <,>()", 4) + + lines =<< trim END + vim9script + + class A + static def Fn() + enddef + endclass + A.Fn<>() + END + v9.CheckSourceFailureList(lines, ["E1555: Empty type list specified for generic function '<>()'"]) + + lines =<< trim END + vim9script + + class A + static def Fn() + enddef + endclass + A.Fn() + END + v9.CheckSourceFailure(lines, "E1556: Too many types specified for generic function 'Fn'", 7) + + lines =<< trim END + vim9script + + class A + static def Fn() + enddef + endclass + A.Fn() + END + v9.CheckSourceFailure(lines, "E1557: Not enough types specified for generic function 'Fn'", 7) + + lines =<< trim END + vim9script + + class A + static def Fn() + enddef + endclass + A.Fn() + END + v9.CheckSourceFailure(lines, "E1559: Type arguments missing for generic function 'Fn'", 7) + + lines =<< trim END + vim9script + + class A + static def Fn() + enddef + endclass + A.Fn() + END + v9.CheckSourceFailure(lines, 'E1560: Not a generic function: Fn', 7) +enddef + +def Test_generic_class_method_call_from_another_method() + var lines =<< trim END + vim9script + + class A + static def Fn(t: X): X + var n: X = t + return n + enddef + endclass + + def Foo() + assert_equal(['a', 'b'], A.Fn>(['a', 'b'])) + enddef + Foo() + END + v9.CheckSourceSuccess(lines) + + lines =<< trim END + vim9script + + class A + static def Fn() + enddef + endclass + + def Foo() + A.Fn<>() + enddef + defcompile + END + v9.CheckSourceFailureList(lines, ["E1555: Empty type list specified for generic function '<>()'"]) + + lines =<< trim END + vim9script + + class A + static def Fn() + enddef + endclass + + def Foo() + A.Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1556: Too many types specified for generic function 'Fn'", 1) + + lines =<< trim END + vim9script + + class A + static def Fn() + enddef + endclass + + def Foo() + A.Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1557: Not enough types specified for generic function 'Fn'", 1) + + lines =<< trim END + vim9script + + class A + static def Fn() + enddef + endclass + + def Foo() + A.Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1559: Type arguments missing for generic function 'Fn'", 1) + + lines =<< trim END + vim9script + + class A + static def Fn() + enddef + endclass + + def Foo() + A.Fn() + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1560: Not a generic function: Fn', 1) +enddef + +" Test for using a generic funcref from another method +def Test_generic_funcref_use_from_def_method() + var lines =<< trim END + vim9script + + def Foo(t: T): T + return t + enddef + + def Fx() + var Fn = Foo> + var x: list = Fn(['abc', 'b']) + assert_equal(['abc', 'b'], x) + enddef + Fx() + END + v9.CheckSourceSuccess(lines) + + # Assigning a generic function without specifying any type arguments + lines =<< trim END + vim9script + + def Foo() + enddef + + def Fx() + var Fn = Foo + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 1) + + # Assigning a generic function specifying additional type arguments + lines =<< trim END + vim9script + + def Foo() + enddef + + def Fx() + var Fn = Foo + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1556: Too many types specified for generic function', 1) + + # Assigning a generic function specifying less type arguments + lines =<< trim END + vim9script + + def Foo() + enddef + + def Fx() + var Fn = Foo + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1557: Not enough types specified for generic function', 1) + + # Assigning a generic function specifying an empty type argument list + lines =<< trim END + vim9script + + def Foo() + enddef + + def Fx() + var Fn = Foo<> + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1555: Empty type list specified for generic function', 1) + + # Assigning a generic function specifying only the opening bracket + lines =<< trim END + vim9script + + def Foo() + enddef + + def Fx() + var Fn = Foo< + enddef + defcompile + END + v9.CheckSourceFailure(lines, "E1554: Missing '>' in generic function: <", 1) + + # Assigning a generic function without specifying the closing bracket + lines =<< trim END + vim9script + + def Foo() + enddef + + def Fx() + var Fn = Foo' in generic function: () + enddef + + def Fx() + var Fn = Foo' in generic function: + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1560: Not a generic function', 1) + + # Call a generic funcref using a different argument type + lines =<< trim END + vim9script + + def Foo(t: T) + enddef + + def Fx() + var Fn = Foo + Fn(10) + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number', 2) + + # Assign a generic funcref return value to a variable of different type + lines =<< trim END + vim9script + + def Foo(t: T): T + return t + enddef + + def Fx() + var Fn = Foo + var x: number = Fn('abc') + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected number but got string', 2) +enddef + +def Test_generic_vim9_lambda() + var lines =<< trim END + vim9script + + def Fn() + var Lambda = (x: A, y: B): C => x + y + assert_equal(30, Lambda(10, 20)) + enddef + + Fn() + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for using a generic type in a nested def function +def Test_generic_nested_def() + var lines =<< trim END + vim9script + def F1() + def F2(x: A, y: B): C + return x + y + enddef + assert_equal(30, F2(10, 20)) + enddef + F1() + END + v9.CheckSourceSuccess(lines) + + # Lambda function in a nested def function + lines =<< trim END + vim9script + def F1() + def F2(): func + var Lambda = (x: A, y: B): C => x + y + return Lambda + enddef + var Fx = F2() + assert_equal(60, Fx(20, 40)) + enddef + F1() + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for type substitution in a generic function call. Only the generic types +" should be substituted. Other "any" types should be ignored. +def Test_generic_type_substitution() + var lines =<< trim END + vim9script + def Fn(a: any, b: T): any + return a + enddef + assert_equal('abc', Fn('abc', 20)) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for a global generic function g:MyFunc +def Test_generic_global_function() + var lines =<< trim END + vim9script + def g:Fn1(a: T): T + return a + enddef + assert_equal('abc', g:Fn1('abc')) + + def Foo() + assert_equal(['a', 'b'], g:Fn1>(['a', 'b'])) + enddef + Foo() + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for calling a nested generic function +def Test_generic_function_call_nested() + var lines =<< trim END + vim9script + + def Id_(o: T): T + return o + enddef + + class Test + def Id(o: U): U + return Id_(o) + enddef + endclass + + assert_equal(".", Id_(".")) + assert_equal(0, Id_(0)) + assert_equal(false, Id_(false)) + assert_equal(false, Id_(false)) + assert_equal(0, Id_(0)) + assert_equal(".", Id_(".")) + assert_equal([], Test.new().Id([])) + assert_equal(".", Test.new().Id(".")) + assert_equal(0, Test.new().Id(0)) + assert_equal(false, Test.new().Id(false)) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for an imported generic function foo#MyFunc +def Test_generic_import() + var lines =<< trim END + vim9script + export def Fn(a: A, b: B): B + return b + enddef + export def Foobar() + enddef + END + writefile(lines, 'Ximport_generic.vim', 'D') + + lines =<< trim END + vim9script + import "./Ximport_generic.vim" as Foo + assert_equal(20, Foo.Fn('abc', 20)) + def MyFunc() + assert_equal('xyz', Foo.Fn(30, 'xyz')) + enddef + MyFunc() + END + v9.CheckSourceSuccess(lines) + + lines =<< trim END + vim9script + import "./Ximport_generic.vim" as Foo + + Foo.Fn('abc', 20) + END + v9.CheckSourceFailure(lines, 'E1557: Not enough types specified for generic function', 4) + + lines =<< trim END + vim9script + import "./Ximport_generic.vim" as Foo + + def MyFunc() + Foo.Fn('abc', 20) + enddef + MyFunc() + END + v9.CheckSourceFailure(lines, 'E1557: Not enough types specified for generic function', 1) + + lines =<< trim END + vim9script + import "./Ximport_generic.vim" as Foo + + Foo.Fn(10, 20) + END + v9.CheckSourceFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number', 4) + + lines =<< trim END + vim9script + import "./Ximport_generic.vim" as Foo + + def MyFunc() + Foo.Fn(10, 20) + enddef + MyFunc() + END + v9.CheckSourceFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number', 1) + + lines =<< trim END + vim9script + import "./Ximport_generic.vim" as Foo + + Foo.Fn(10, 20) + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 4) + + lines =<< trim END + vim9script + import "./Ximport_generic.vim" as Foo + + def MyFunc() + Foo.Fn(10, 20) + enddef + MyFunc() + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 1) + + lines =<< trim END + vim9script + import "./Ximport_generic.vim" as Foo + + Foo.Fn after after () + END + v9.CheckSourceFailure(lines, "E1560: Not a generic function", 4) + + lines =<< trim END + vim9script + import "./Ximport_generic.vim" as Foo + + def MyFunc() + Foo.Foobar() + enddef + MyFunc() + END + v9.CheckSourceFailure(lines, "E1560: Not a generic function: Foo", 1) + + lines =<< trim END + vim9script + import "./Ximport_generic.vim" as Foo + + Foo.Fx('abc', 20) + END + v9.CheckSourceFailure(lines, 'E1048: Item not found in script: Fx', 4) + + lines =<< trim END + vim9script + import "./Ximport_generic.vim" as Foo + + def MyFunc() + Foo.Fx('abc', 20) + enddef + MyFunc() + END + v9.CheckSourceFailure(lines, 'E1048: Item not found in script: Fx', 1) + + # Source the script twice + lines =<< trim END + vim9script + export def Fn(a: list) + enddef + END + writefile(lines, 'Ximport_generic_2.vim', 'D') + + lines =<< trim END + vim9script + import "./Ximport_generic_2.vim" as Foo + + Foo.Fn>>([[{}]]) + END + :new + setline(1, lines) + :source + :source + :bw! +enddef + +" Test for disassembling a generic function +def Test_generic_function_disassemble() + var lines =<< trim END + vim9script + def Fn(): A + var x: A + var y: B + [x, y] = g:values + return x + enddef + g:instr = execute('disassemble Fn, dict>') + END + v9.CheckScriptSuccess(lines) + assert_match('\d\+_Fn, dict>\_s*' .. + 'var x: A\_s*' .. + '0 NEWLIST size 0\_s*' .. + '1 SETTYPE list\_s*' .. + '2 STORE $0\_s*' .. + 'var y: B\_s*' .. + '3 NEWDICT size 0\_s*' .. + '4 SETTYPE dict\_s*' .. + '5 STORE $1\_s*' .. + '\[x, y\] = g:values\_s*' .. + '6 LOADG g:values\_s*\_s*' .. + '7 CHECKTYPE list stack\[-1\]\_s*' .. + '8 CHECKLEN 2\_s*' .. + '9 ITEM 0\_s*' .. + '10 CHECKTYPE list stack\[-1\] var 1\_s*' .. + '11 SETTYPE list\_s*' .. + '12 STORE $0\_s*' .. + '13 ITEM 1\_s*' .. + '14 CHECKTYPE dict stack\[-1\] var 2\_s*' .. + '15 SETTYPE dict\_s*' .. + '16 STORE $1\_s*' .. + '17 DROP\_s*' .. + 'return x\_s*' .. + '18 LOAD $0\_s*' .. + '19 RETURN', g:instr) + unlet g:instr + + lines =<< trim END + vim9script + disassemble Fn + END + v9.CheckScriptFailure(lines, "E1554: Missing '>' in generic function: ", 2) + + lines =<< trim END + vim9script + disassemble Fn> + END + v9.CheckScriptFailure(lines, 'E1061: Cannot find function Fn>', 2) + + lines =<< trim END + vim9script + disassemble Fn' in generic function: ' in generic function: <", 2) + + lines =<< trim END + vim9script + def Fn() + enddef + disassemble Fn + END + v9.CheckScriptFailure(lines, 'E1560: Not a generic function:', 4) + + lines =<< trim END + vim9script + def Fn() + enddef + disassemble Fn + END + v9.CheckScriptFailure(lines, 'E1556: Too many types specified for generic function', 4) + + lines =<< trim END + vim9script + def Fn() + enddef + disassemble Fn + END + v9.CheckScriptFailure(lines, 'E1559: Type arguments missing for generic function', 4) + + lines =<< trim END + vim9script + def Fn() + enddef + disassemble Fn + END + v9.CheckScriptFailure(lines, 'E1557: Not enough types specified for generic function', 4) + + lines =<< trim END + vim9script + def Fn() + enddef + disassemble Fn<> + END + v9.CheckScriptFailure(lines, "E1555: Empty type list specified for generic function '<>'", 4) +enddef + +" Test for disassembling a generic function calling another generic function +def Test_nested_generic_func_call_disassemble() + var lines =<< trim END + vim9script + def Fn1(o: T): T + return o + enddef + + def Fn2(o: U): U + return Fn1(o) + enddef + + g:instr1 = execute('disassemble Fn2') + g:instr2 = execute('disassemble Fn2') + END + v9.CheckScriptSuccess(lines) + assert_match('\d\+_Fn2\_s*' .. + 'return Fn1(o)\_s*' .. + '0 LOAD arg\[-1\]\_s*' .. + '1 DCALL \d\+_Fn1(argc 1)\_s*' .. + '2 RETURN\_s*', g:instr1) + assert_match('\d\+_Fn2\_s*' .. + 'return Fn1(o)\_s*' .. + '0 LOAD arg\[-1\]\_s*' .. + '1 DCALL \d\+_Fn1(argc 1)\_s*' .. + '2 RETURN\_s*', g:instr2) + + unlet g:instr1 + unlet g:instr2 +enddef + +" Test for disassembling a generic object method +def Test_generic_disassemble_generic_obj_method() + var lines =<< trim END + vim9script + class Foo + def Fn() + var x: A + var y: B + [x, y] = g:values + enddef + endclass + g:instr = execute('disassemble Foo.Fn, dict>') + END + v9.CheckScriptSuccess(lines) + assert_match('Fn, dict>\_s*' .. + 'var x: A\_s*' .. + '0 NEWLIST size 0\_s*' .. + '1 SETTYPE list\_s*' .. + '2 STORE $1\_s*' .. + 'var y: B\_s*' .. + '3 NEWDICT size 0\_s*' .. + '4 SETTYPE dict\_s*' .. + '5 STORE $2\_s*' .. + '\[x, y\] = g:values\_s*' .. + '6 LOADG g:values\_s*' .. + '7 CHECKTYPE list stack\[-1\]\_s*' .. + '8 CHECKLEN 2\_s*' .. + '9 ITEM 0\_s*' .. + '10 CHECKTYPE list stack\[-1\] var 1\_s*' .. + '11 SETTYPE list\_s*' .. + '12 STORE $1\_s*' .. + '13 ITEM 1\_s*' .. + '14 CHECKTYPE dict stack\[-1\] var 2\_s*' .. + '15 SETTYPE dict\_s*' .. + '16 STORE $2\_s*' .. + '17 DROP\_s*' .. + '18 RETURN void', g:instr) + unlet g:instr + + lines =<< trim END + vim9script + class Foo + def Fn() + enddef + endclass + disassemble Foo.Fn + END + v9.CheckScriptFailure(lines, 'E1553: Missing comma after type in generic function: ', 6) + + lines =<< trim END + vim9script + class Foo + endclass + disassemble Foo.Fn> + END + v9.CheckScriptFailure(lines, 'E1337: Class variable "Fn" not found in class "Foo"', 4) + + lines =<< trim END + vim9script + class Foo + def Fn() + enddef + endclass + disassemble Foo.Fn() + enddef + endclass + disassemble Foo.Fn< + END + v9.CheckScriptFailure(lines, 'E475: Invalid argument: Foo.Fn<', 6) + + lines =<< trim END + vim9script + class Foo + def Fn() + enddef + endclass + disassemble Foo.Fn + END + v9.CheckScriptFailure(lines, 'E1560: Not a generic function:', 6) + + lines =<< trim END + vim9script + class Foo + def Fn() + enddef + endclass + disassemble Foo.Fn + END + v9.CheckScriptFailure(lines, 'E1556: Too many types specified for generic function', 6) + + lines =<< trim END + vim9script + class Foo + def Fn() + enddef + endclass + disassemble Foo.Fn + END + v9.CheckScriptFailure(lines, 'E1559: Type arguments missing for generic function', 6) + + lines =<< trim END + vim9script + class Foo + def Fn() + enddef + endclass + disassemble Foo.Fn + END + v9.CheckScriptFailure(lines, 'E1557: Not enough types specified for generic function', 6) + + lines =<< trim END + vim9script + class Foo + def Fn() + enddef + endclass + disassemble Foo.Fn<> + END + v9.CheckScriptFailure(lines, "E1555: Empty type list specified for generic function 'Fn'", 6) +enddef + +" Test for disassembling a generic class method +def Test_generic_disassemble_generic_class_method() + var lines =<< trim END + vim9script + class Foo + static def Fn() + var x: A + var y: B + [x, y] = g:values + enddef + endclass + g:instr = execute('disassemble Foo.Fn, dict>') + END + v9.CheckScriptSuccess(lines) + assert_match('Fn, dict>\_s*' .. + 'var x: A\_s*' .. + '0 NEWLIST size 0\_s*' .. + '1 SETTYPE list\_s*' .. + '2 STORE $0\_s*' .. + 'var y: B\_s*' .. + '3 NEWDICT size 0\_s*' .. + '4 SETTYPE dict\_s*' .. + '5 STORE $1\_s*' .. + '\[x, y\] = g:values\_s*' .. + '6 LOADG g:values\_s*' .. + '7 CHECKTYPE list stack\[-1\]\_s*' .. + '8 CHECKLEN 2\_s*' .. + '9 ITEM 0\_s*' .. + '10 CHECKTYPE list stack\[-1\] var 1\_s*' .. + '11 SETTYPE list\_s*' .. + '12 STORE $0\_s*' .. + '13 ITEM 1\_s*' .. + '14 CHECKTYPE dict stack\[-1\] var 2\_s*' .. + '15 SETTYPE dict\_s*' .. + '16 STORE $1\_s*' .. + '17 DROP\_s*' .. + '18 RETURN void', g:instr) + unlet g:instr + + lines =<< trim END + vim9script + class Foo + static def Fn() + enddef + endclass + disassemble Foo.Fn + END + v9.CheckScriptFailure(lines, 'E1553: Missing comma after type in generic function: ', 6) + + lines =<< trim END + vim9script + class Foo + static def Fn() + enddef + endclass + disassemble Foo.Fn() + enddef + endclass + disassemble Foo.Fn< + END + v9.CheckScriptFailure(lines, 'E475: Invalid argument: Foo.Fn<', 6) + + lines =<< trim END + vim9script + class Foo + static def Fn() + enddef + endclass + disassemble Foo.Fn + END + v9.CheckScriptFailure(lines, 'E1560: Not a generic function:', 6) + + lines =<< trim END + vim9script + class Foo + static def Fn() + enddef + endclass + disassemble Foo.Fn + END + v9.CheckScriptFailure(lines, 'E1556: Too many types specified for generic function', 6) + + lines =<< trim END + vim9script + class Foo + static def Fn() + enddef + endclass + disassemble Foo.Fn + END + v9.CheckScriptFailure(lines, 'E1559: Type arguments missing for generic function', 6) + + lines =<< trim END + vim9script + class Foo + static def Fn() + enddef + endclass + disassemble Foo.Fn + END + v9.CheckScriptFailure(lines, 'E1557: Not enough types specified for generic function', 6) + + lines =<< trim END + vim9script + class Foo + static def Fn() + enddef + endclass + disassemble Foo.Fn<> + END + v9.CheckScriptFailure(lines, "E1555: Empty type list specified for generic function 'Fn'", 6) +enddef + +" Test for disassembling a generic function using a Funcref variable +def Test_generic_disassemble_variable() + var lines =<< trim END + vim9script + + def Foo() + echomsg "Foo" + enddef + + var Fn = Foo + disassemble Fn + g:instr = execute('disassemble Fn') + END + v9.CheckScriptSuccess(lines) + assert_match('\d\+_Foo\_s*' .. + 'echomsg "Foo"\_s*' .. + '0 PUSHS "Foo"\_s*' .. + '1 ECHOMSG 1\_s*' .. + '2 RETURN void\_s*', g:instr) + unlet g:instr +enddef + +def Test_generic_duplicate_names() + var lines =<< trim END + vim9script + def Fn() + enddef + defcompile + END + v9.CheckScriptFailure(lines, 'E1561: Duplicate type variable name: A', 2) + + lines =<< trim END + vim9script + class A + endclass + def Fn() + enddef + defcompile + END + v9.CheckScriptFailure(lines, 'E1041: Redefining script item: "A"', 4) + + lines =<< trim END + vim9script + type A = number + def Fn() + enddef + defcompile + END + v9.CheckScriptFailure(lines, 'E1041: Redefining script item: "A"', 3) + + lines =<< trim END + vim9script + var B = 'abc' + def Fn() + enddef + defcompile + END + v9.CheckScriptFailure(lines, 'E1041: Redefining script item: "B"', 3) + + lines =<< trim END + vim9script + def Fn1() + enddef + def Fn2() + enddef + defcompile + END + v9.CheckScriptSuccess(lines) + + lines =<< trim END + vim9script + + def I(x: A): A + return x + enddef + + def Id(x: I): I + return x + enddef + END + v9.CheckScriptFailure(lines, 'E1041: Redefining script item: "I"', 7) +enddef + +" Test for nested generic functions +def Test_generic_nested_functions() + var lines =<< trim END + vim9script + def Fn(t: T): T + def Fx(a: A): A + return a + enddef + return Fx(t) + enddef + assert_equal(100, Fn(100)) + END + v9.CheckScriptSuccess(lines) + + # Use the generic type from the outer generic function + lines =<< trim END + vim9script + def Fn(b: B): B + def Fx(t: T): T + return t + enddef + return Fx(b) + enddef + assert_equal(100, Fn(100)) + assert_equal('abc', Fn('abc')) + END + v9.CheckScriptSuccess(lines) + + # duplicate definition + lines =<< trim END + vim9script + def Fn() + def Fn() + enddef + enddef + defcompile + END + v9.CheckScriptFailure(lines, 'E1073: Name already defined: Fn', 1) + + # overlaps with a script-local function + lines =<< trim END + vim9script + def Fx() + enddef + def Fn() + def Fx() + enddef + enddef + defcompile + END + v9.CheckScriptFailure(lines, 'E1073: Name already defined: Fx', 1) + + # overlaps with another nested function + lines =<< trim END + vim9script + def Fn() + def Fx() + enddef + def Fx() + enddef + enddef + defcompile + END + v9.CheckScriptFailure(lines, 'E1073: Name already defined: Fx', 3) + + # Empty list of types + lines =<< trim END + vim9script + def Fn() + def Fx<>() + enddef + enddef + defcompile + END + v9.CheckScriptFailure(lines, 'E1555: Empty type list specified for generic function', 3) + + lines =<< trim END + vim9script + def Fn() + def Fx<,>() + enddef + enddef + defcompile + END + v9.CheckScriptFailure(lines, 'E1008: Missing after <', 3) + + # missing closing bracket in the inner generic function + lines =<< trim END + vim9script + def Fn() + def Fx() + def Fx() + enddef + enddef + defcompile + END + v9.CheckScriptFailure(lines, 'E1552: Type variable name must start with an uppercase letter: a>()', 1) + + # duplicate generic type + lines =<< trim END + vim9script + def Fn() + def Fx() + enddef + enddef + defcompile + END + v9.CheckScriptFailure(lines, 'E1561: Duplicate type variable name: T', 1) +enddef + +" Test for using a generic function in call() as a string +def Test_generic_function_use_in_call_function_as_string() + var lines =<< trim END + vim9script + def Fn(a: A): A + return a + enddef + assert_equal(['a', 'b'], call("Fn>", [['a', 'b']])) + END + v9.CheckSourceSuccess(lines) + + # Test for passing more types + lines =<< trim END + vim9script + def Fn() + enddef + call("Fn", []) + END + v9.CheckSourceFailure(lines, 'E1556: Too many types specified for generic function', 4) + + # Test for passing less types + lines =<< trim END + vim9script + def Fn() + enddef + call("Fn", []) + END + v9.CheckSourceFailure(lines, 'E1557: Not enough types specified for generic function', 4) + + # Test for passing empty types + lines =<< trim END + vim9script + def Fn() + enddef + call("Fn<>", []) + END + v9.CheckSourceFailure(lines, 'E1555: Empty type list specified for generic function', 4) + + # Test for passing no types + lines =<< trim END + vim9script + def Fn() + enddef + call("Fn", []) + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 4) + + # Test for missing types and closing bracket + lines =<< trim END + vim9script + def Fn() + enddef + call("Fn<", []) + END + v9.CheckSourceFailure(lines, "E1554: Missing '>' in generic function: <", 4) + + # Test for missing types + lines =<< trim END + vim9script + def Fn() + enddef + call("Fn' in generic function: () + enddef + call("Fn' in generic function: () + enddef + call("Fn", []) + END + v9.CheckSourceFailure(lines, "E1069: White space required after ','", 4) + + # Test for missing types + lines =<< trim END + vim9script + def Fn() + enddef + call("Fn", []) + END + v9.CheckSourceFailure(lines, 'E1008: Missing after ', 4) + + # Test for calling a non-existing generic function + lines =<< trim END + vim9script + call("FooBar", []) + END + v9.CheckSourceFailure(lines, 'E1558: Unknown generic function', 2) + + # Test for calling a regular function as a generic function + lines =<< trim END + vim9script + def Fn() + enddef + call("Fn", []) + END + v9.CheckSourceFailure(lines, 'E1560: Not a generic function', 4) +enddef + +" Test for using a generic function in call() as a string in a method +def Test_generic_use_in_call_func_as_string_in_method() + var lines =<< trim END + vim9script + def Fn(a: A): A + return a + enddef + def Foo() + assert_equal(['a', 'b'], call("Fn>", [['a', 'b']])) + enddef + Foo() + END + v9.CheckSourceSuccess(lines) + + # Test for passing more types + lines =<< trim END + vim9script + def Fn() + enddef + def Foo() + call("Fn", []) + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1556: Too many types specified for generic function', 1) + + # Test for passing less types + lines =<< trim END + vim9script + def Fn() + enddef + def Foo() + call("Fn", []) + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1557: Not enough types specified for generic function', 1) + + # Test for passing empty types + lines =<< trim END + vim9script + def Fn() + enddef + def Foo() + call("Fn<>", []) + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1555: Empty type list specified for generic function', 1) + + # Test for passing no types + lines =<< trim END + vim9script + def Fn() + enddef + def Foo() + call("Fn", []) + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 1) + + # Test for missing types and closing bracket + lines =<< trim END + vim9script + def Fn() + enddef + def Foo() + call("Fn<", []) + enddef + Foo() + END + v9.CheckSourceFailure(lines, "E1554: Missing '>' in generic function: <", 1) + + # Test for missing types + lines =<< trim END + vim9script + def Fn() + enddef + def Foo() + call("Fn' in generic function: () + enddef + def Foo() + call("Fn' in generic function: () + enddef + def Foo() + call("Fn", []) + enddef + Foo() + END + v9.CheckSourceFailure(lines, "E1069: White space required after ','", 1) + + # Test for missing types + lines =<< trim END + vim9script + def Fn() + enddef + def Foo() + call("Fn", []) + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1008: Missing after ', 1) + + # Test for calling a non-existing generic function + lines =<< trim END + vim9script + def Foo() + call("FooBar", []) + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1558: Unknown generic function', 1) + + # Test for calling a regular function as a generic function + lines =<< trim END + vim9script + def Fn() + enddef + def Foo() + call("Fn", []) + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1560: Not a generic function', 1) +enddef + +" Test for using a generic function in call() as a funcref +def Test_generic_function_use_in_call_function_as_funcref() + var lines =<< trim END + vim9script + def Fn(a: A): A + return a + enddef + assert_equal({a: 'xyz'}, call(Fn>, [{a: 'xyz'}])) + END + v9.CheckSourceSuccess(lines) + + # Test for passing more types + lines =<< trim END + vim9script + def Fn() + enddef + call(Fn, []) + END + v9.CheckSourceFailure(lines, 'E1556: Too many types specified for generic function', 4) + + # Test for passing less types + lines =<< trim END + vim9script + def Fn() + enddef + call(Fn, []) + END + v9.CheckSourceFailure(lines, 'E1557: Not enough types specified for generic function', 4) + + # Test for passing empty types + lines =<< trim END + vim9script + def Fn() + enddef + call(Fn<>, []) + END + v9.CheckSourceFailure(lines, 'E1555: Empty type list specified for generic function', 4) + + # Test for passing no types + lines =<< trim END + vim9script + def Fn() + enddef + call(Fn, []) + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 4) + + # Test for missing types and closing bracket + lines =<< trim END + vim9script + def Fn() + enddef + call(Fn<, []) + END + v9.CheckSourceFailure(lines, "E1008: Missing after <", 4) + + # Test for missing types + lines =<< trim END + vim9script + def Fn() + enddef + call(Fn after () + enddef + call(Fn() + enddef + call(Fn, []) + END + v9.CheckSourceFailure(lines, "E1069: White space required after ','", 4) + + # Test for missing types + lines =<< trim END + vim9script + def Fn() + enddef + call(Fn, []) + END + v9.CheckSourceFailure(lines, 'E1008: Missing after ', 4) + + # Test for calling a non-existing generic function + lines =<< trim END + vim9script + call(FooBar, []) + END + v9.CheckSourceFailure(lines, 'E121: Undefined variable: FooBar', 2) + + # Test for calling a regular function as a generic function + lines =<< trim END + vim9script + def Fn() + enddef + call(Fn, []) + END + v9.CheckSourceFailure(lines, 'E1560: Not a generic function', 4) +enddef + +" Test for using a generic function with the :call command +def Test_generic_function_use_call_cmd() + var lines =<< trim END + vim9script + var funcArgs = {} + def Fn(a: A) + funcArgs = a + enddef + call Fn>({a: 'xyz'}) + assert_equal({a: 'xyz'}, funcArgs) + END + v9.CheckSourceSuccess(lines) + + # Test for passing extra types + lines =<< trim END + vim9script + def Fn() + enddef + call Fn() + END + v9.CheckSourceFailure(lines, 'E1556: Too many types specified for generic function', 4) + + # Test for passing less types + lines =<< trim END + vim9script + def Fn() + enddef + call Fn() + END + v9.CheckSourceFailure(lines, 'E1557: Not enough types specified for generic function', 4) + + # Test for passing empty types + lines =<< trim END + vim9script + def Fn() + enddef + call Fn<>() + END + v9.CheckSourceFailure(lines, 'E1555: Empty type list specified for generic function', 4) + + # Test for passing no types + lines =<< trim END + vim9script + def Fn() + enddef + call Fn() + END + v9.CheckSourceFailure(lines, 'E1559: Type arguments missing for generic function', 4) + + # Test for missing types and closing bracket + lines =<< trim END + vim9script + def Fn() + enddef + call Fn<() + END + v9.CheckSourceFailure(lines, "E1008: Missing after <", 4) + + # Test for missing types + lines =<< trim END + vim9script + def Fn() + enddef + call Fn after () + enddef + call Fn() + enddef + call Fn() + END + v9.CheckSourceFailure(lines, "E1069: White space required after ','", 4) + + # Test for missing types + lines =<< trim END + vim9script + def Fn() + enddef + call Fn() + END + v9.CheckSourceFailure(lines, 'E1008: Missing after ', 4) + + # Test for calling a non-existing generic function + lines =<< trim END + vim9script + call FooBar() + END + v9.CheckSourceFailure(lines, 'E1558: Unknown generic function', 2) + + # Test for calling a regular function as a generic function + lines =<< trim END + vim9script + def Fn() + enddef + call Fn() + END + v9.CheckSourceFailure(lines, 'E1560: Not a generic function', 4) +enddef + +def Test_generic_function_with_sort() + var lines =<< trim END + vim9script + + def g:CompareByLength(a: T, b: T): number + return string(a)->strlen() - string(b)->strlen() + enddef + + var l = ['aaaa', 'aaa', 'a', 'aa'] + ->sort((a, b) => g:CompareByLength(a, b)) + assert_equal(['a', 'aa', 'aaa', 'aaaa'], l) + + l = ['aaaa', 'aaa', 'a', 'aa'] + ->sort(g:CompareByLength) + assert_equal(['a', 'aa', 'aaa', 'aaaa'], l) + + l = ['aaaa', 'aaa', 'a', 'aa'] + ->sort(function(g:CompareByLength)) + assert_equal(['a', 'aa', 'aaa', 'aaaa'], l) + + l = ['aaaa', 'aaa', 'a', 'aa'] + ->sort(funcref('g:CompareByLength')) + assert_equal(['a', 'aa', 'aaa', 'aaaa'], l) + + l = ['aaaa', 'aaa', 'a', 'aa'] + ->sort(function('g:CompareByLength')) + assert_equal(['a', 'aa', 'aaa', 'aaaa'], l) + END + v9.CheckSourceSuccess(lines) + + lines =<< trim END + vim9script + + def Foo() + var l = ['aaaa', 'aaa', 'a', 'aa'] + ->sort((a, b) => g:CompareByLength(a, b)) + assert_equal(['a', 'aa', 'aaa', 'aaaa'], l) + + l = ['aaaa', 'aaa', 'a', 'aa'] + ->sort(g:CompareByLength) + assert_equal(['a', 'aa', 'aaa', 'aaaa'], l) + + l = ['aaaa', 'aaa', 'a', 'aa'] + ->sort(function(g:CompareByLength)) + assert_equal(['a', 'aa', 'aaa', 'aaaa'], l) + + l = ['aaaa', 'aaa', 'a', 'aa'] + ->sort(funcref('g:CompareByLength')) + assert_equal(['a', 'aa', 'aaa', 'aaaa'], l) + + l = ['aaaa', 'aaa', 'a', 'aa'] + ->sort(function('g:CompareByLength')) + assert_equal(['a', 'aa', 'aaa', 'aaaa'], l) + enddef + Foo() + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for using a generic type in the return value of a function. +def Test_generic_function_func_ret_val() + var lines =<< trim END + vim9script + + def Id(): func(U): U + return (X: U) => X + enddef + + assert_equal(123, Id()(123)) + assert_equal(321, Id()(Id())(321)) + const F: func(number): number = Id() + var G: func(number): number + G = Id()(F) + assert_equal(true, typename(F) == typename(G)) + assert_equal(456, G(456)) + + G = Id()(F) + assert_equal(456, G(456)) + END + v9.CheckSourceSuccess(lines) + + lines =<< trim END + vim9script + + def Id(): func(U): U + return (X: U) => X + enddef + + def Foo() + assert_equal(123, Id()(123)) + assert_equal(321, Id()(Id())(321)) + const F: func(number): number = Id() + var G: func(number): number + G = Id()(F) + assert_equal(true, typename(F) == typename(G)) + assert_equal(456, G(456)) + + G = Id()(F) + assert_equal(456, G(456)) + enddef + Foo() + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for recursively calling a generic function +def Test_generic_function_recursive_call() + var lines =<< trim END + vim9script + + def Y0_(): func(func(U): U): U + return (F: func(U): U) => F(Y0_()(F)) + enddef + + const Partial_: func(func(any): any): any = Y0_() + Partial_((x: any) => x) + END + v9.CheckSourceFailure(lines, "E132: Function call depth is higher than 'maxfuncdepth'", 1) + + lines =<< trim END + vim9script + + def Y0_(): func(func(U): U): U + return (F: func(U): U) => F(Y0_()(F)) + enddef + + def Foo() + const Partial_: func(func(any): any): any = Y0_() + Partial_((x: any) => x) + enddef + Foo() + END + v9.CheckSourceFailure(lines, "E132: Function call depth is higher than 'maxfuncdepth'", 1) +enddef + +" Test for overriding a generic object method +def Test_generic_object_method_override() + var lines =<< trim END + vim9script + + class A + def Fn(t: T): T + return t + enddef + endclass + + class B extends A + def Fn(t: T): T + return t + enddef + endclass + + var a = A.new() + var b = B.new() + assert_equal(10, a.Fn(10)) + assert_equal('abc', b.Fn('abc')) + assert_equal(['a', 'b'], a.Fn>(['a', 'b'])) + assert_equal({a: 10, b: 20}, b.Fn>({a: 10, b: 20})) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for failures in overriding a generic object method +def Test_generic_object_method_override_fails() + var lines =<< trim END + vim9script + + class A + def Fn(t: T): T + return t + enddef + endclass + + class B extends A + def Fn(t: number): number + return t + enddef + endclass + END + v9.CheckSourceFailure(lines, 'E1432: Overriding generic method "Fn" in class "A" with a concrete method', 13) + + lines =<< trim END + vim9script + + class A + def Fn(t: number): number + return t + enddef + endclass + + class B extends A + def Fn(t: T): T + return t + enddef + endclass + END + v9.CheckSourceFailure(lines, 'E1433: Overriding concrete method "Fn" in class "A" with a generic method', 13) + + lines =<< trim END + vim9script + + class A + def Fn(t: T): T + return t + enddef + endclass + + class B extends A + def Fn(t: X): X + return t + enddef + endclass + END + v9.CheckSourceFailure(lines, 'E1434: Mismatched number of type variables for generic method "Fn" in class "A"', 13) + + lines =<< trim END + vim9script + + class A + def Fn(t: X): X + return t + enddef + endclass + + class B extends A + def Fn(t: T): T + return t + enddef + endclass + END + v9.CheckSourceFailure(lines, 'E1434: Mismatched number of type variables for generic method "Fn" in class "A"', 13) +enddef + +" Test for overriding a generic abstract object method +def Test_generic_abstract_object_method_override() + var lines =<< trim END + vim9script + + abstract class A + abstract def Fn(t: T): T + endclass + + class B extends A + def Fn(t: T): T + return t + enddef + endclass + + var b = B.new() + assert_equal('abc', b.Fn('abc')) + assert_equal({a: 10, b: 20}, b.Fn>({a: 10, b: 20})) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for failures in overriding a generic abstract method +def Test_generic_abstract_method_override_fails() + var lines =<< trim END + vim9script + + abstract class A + abstract def Fn(t: T): T + endclass + + class B extends A + def Fn(t: number): number + return t + enddef + endclass + END + v9.CheckSourceFailure(lines, 'E1432: Overriding generic method "Fn" in class "A" with a concrete method', 11) + + lines =<< trim END + vim9script + + abstract class A + abstract def Fn(t: number): number + endclass + + class B extends A + def Fn(t: T): T + return t + enddef + endclass + END + v9.CheckSourceFailure(lines, 'E1433: Overriding concrete method "Fn" in class "A" with a generic method', 11) + + lines =<< trim END + vim9script + + abstract class A + abstract def Fn(t: T): T + endclass + + class B extends A + def Fn(t: X): X + return t + enddef + endclass + END + v9.CheckSourceFailure(lines, 'E1434: Mismatched number of type variables for generic method "Fn" in class "A"', 11) + + lines =<< trim END + vim9script + + abstract class A + abstract def Fn(t: X): X + endclass + + class B extends A + def Fn(t: T): T + return t + enddef + endclass + END + v9.CheckSourceFailure(lines, 'E1434: Mismatched number of type variables for generic method "Fn" in class "A"', 11) +enddef + +" Test for using a generic method to initialize an object member variable +def Test_generic_method_in_object_member_init_expr() + var lines =<< trim END + vim9script + + def Id(o: T): T + return o + enddef + + class Test + def Id(o: T): T + return o + enddef + + # call the script-local "Id" function + const name: string = Id('Test') + + # call the object "Id" method + const hello: string = this.Id('hello') + endclass + + var t = Test.new() + assert_equal('Test', t.name) + assert_equal('hello', t.hello) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for using generic type variables in the enum constructor +def Test_generic_enum_constructor() + var lines =<< trim END + vim9script + + enum CommonPair + HelloWorld('hello', 'world'), + Booleans(true, false) + + const _fst: any + const _snd: any + + def new(fst: T, snd: U) + this._fst = fst + this._snd = snd + enddef + + def string(): string + return printf("(%s, %s)", this._fst, this._snd) + enddef + endenum + + assert_equal('(hello, world)', string(CommonPair.HelloWorld)) + assert_equal('(true, false)', string(CommonPair.Booleans)) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for errors in using generic type variables in the enum constructor +def Test_generic_enum_constructor_error() + var lines =<< trim END + vim9script + enum Foo + HelloWorld('hello') + def new(a: string) + enddef + endenum + END + v9.CheckSourceFailure(lines, 'E1560: Not a generic function: new', 6) + + lines =<< trim END + vim9script + enum Foo + HelloWorld('hello') + endenum + END + v9.CheckSourceFailure(lines, 'E1560: Not a generic function: new', 4) + + lines =<< trim END + vim9script + enum Foo + HelloWorld('hello') + def new(a: T) + enddef + endenum + END + v9.CheckSourceFailure(lines, "E1559: Type arguments missing for generic function 'new'", 6) + + lines =<< trim END + vim9script + enum Foo + HelloWorld('hello') + def new(a: T) + enddef + endenum + END + v9.CheckSourceFailure(lines, "E1556: Too many types specified for generic function 'new'", 6) + + lines =<< trim END + vim9script + enum Foo + HelloWorld('hello') + def new(a: A) + enddef + endenum + END + v9.CheckSourceFailure(lines, "E1557: Not enough types specified for generic function 'new'", 6) + + lines =<< trim END + vim9script + enum Foo + One(), + HelloWorld<>() + def new() + enddef + endenum + END + v9.CheckSourceFailure(lines, "E1555: Empty type list specified for generic function '<>()'", 4) + + lines =<< trim END + vim9script + enum Foo + HelloWorld() + def new<>() + enddef + endenum + END + v9.CheckSourceFailure(lines, "E1555: Empty type list specified for generic function 'new'", 4) + + lines =<< trim END + vim9script + enum Foo + One(), + Two() + def new() + enddef + endenum + END + v9.CheckSourceFailure(lines, "E1010: Type not recognized: A", 4) +enddef + +" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/userfunc.c b/src/userfunc.c index b9093a0d29..dbd6a4ef15 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -521,12 +521,13 @@ err_ret: */ static int parse_argument_types( - ufunc_T *fp, - garray_T *argtypes, - int varargs, - garray_T *arg_objm, - ocmember_T *obj_members, - int obj_member_count) + ufunc_T *fp, + garray_T *argtypes, + int varargs, + garray_T *arg_objm, + ocmember_T *obj_members, + int obj_member_count, + cctx_T *cctx) { int len = 0; @@ -570,7 +571,7 @@ parse_argument_types( } } else - type = parse_type(&p, &fp->uf_type_list, TRUE); + type = parse_type(&p, &fp->uf_type_list, fp, cctx, TRUE); } if (type == NULL) return FAIL; @@ -606,7 +607,7 @@ parse_argument_types( fp->uf_va_type = &t_list_any; else { - fp->uf_va_type = parse_type(&p, &fp->uf_type_list, TRUE); + fp->uf_va_type = parse_type(&p, &fp->uf_type_list, fp, cctx, TRUE); if (fp->uf_va_type != NULL && fp->uf_va_type->tt_type != VAR_LIST) { semsg(_(e_variable_arguments_type_must_be_list_str), @@ -622,7 +623,7 @@ parse_argument_types( } static int -parse_return_type(ufunc_T *fp, char_u *ret_type) +parse_return_type(ufunc_T *fp, char_u *ret_type, cctx_T *cctx) { if (ret_type == NULL) fp->uf_ret_type = &t_void; @@ -630,7 +631,7 @@ parse_return_type(ufunc_T *fp, char_u *ret_type) { char_u *p = ret_type; - fp->uf_ret_type = parse_type(&p, &fp->uf_type_list, TRUE); + fp->uf_ret_type = parse_type(&p, &fp->uf_type_list, fp, cctx, TRUE); if (fp->uf_ret_type == NULL) { fp->uf_ret_type = &t_void; @@ -697,8 +698,7 @@ get_lambda_name(void) n = vim_snprintf((char *)lambda_name, sizeof(lambda_name), "%d", ++lambda_no); if (n < 1) ret.length = 0; - else - if (n >= (int)sizeof(lambda_name)) + else if (n >= (int)sizeof(lambda_name)) ret.length = sizeof(lambda_name) - 1; else ret.length = (size_t)n; @@ -1211,6 +1211,12 @@ get_function_body( p = skipwhite(p + 1); p += eval_fname_script(p); vim_free(trans_function_name(&p, NULL, TRUE, 0)); + if (vim9_function && *p == '<') + { + // skip generic function + if (skip_generic_func_type_args(&p) == FAIL) + goto theend; + } if (*skipwhite(p) == '(') { if (nesting == MAX_FUNC_NESTING - 1) @@ -1605,14 +1611,15 @@ lambda_function_body( SOURCING_LNUM = sourcing_lnum_top; // parse argument types - if (parse_argument_types(ufunc, argtypes, varargs, NULL, NULL, 0) == FAIL) + if (parse_argument_types(ufunc, argtypes, varargs, NULL, NULL, 0, + NULL) == FAIL) { SOURCING_LNUM = lnum_save; goto erret; } // parse the return type, if any - if (parse_return_type(ufunc, ret_type) == FAIL) + if (parse_return_type(ufunc, ret_type, NULL) == FAIL) goto erret; pt = ALLOC_CLEAR_ONE(partial_T); @@ -1665,7 +1672,8 @@ get_lambda_tv( char_u **arg, typval_T *rettv, int types_optional, - evalarg_T *evalarg) + evalarg_T *evalarg, + cctx_T *cctx) { int evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE); @@ -1849,12 +1857,13 @@ get_lambda_tv( if (types_optional) { if (parse_argument_types(fp, &argtypes, - vim9script && varargs, NULL, NULL, 0) == FAIL) + vim9script && varargs, NULL, NULL, 0, + cctx) == FAIL) goto errret; if (ret_type != NULL) { - fp->uf_ret_type = parse_type(&ret_type, - &fp->uf_type_list, TRUE); + fp->uf_ret_type = parse_type(&ret_type, &fp->uf_type_list, + NULL, cctx, TRUE); if (fp->uf_ret_type == NULL) goto errret; } @@ -2005,10 +2014,20 @@ deref_func_name( return (char_u *)""; // just in case } s = tv->vval.v_string; - *lenp = (int)STRLEN(s); + char_u *p = generic_func_find_open_bracket(s); + if (p != NULL && generic_func_find_open_bracket(name) != NULL) + { + // the funcref name already has generic types but types are + // specified when using the funcref. + *lenp = 0; + return (char_u *)""; + } + if (p != NULL) + *lenp = p - s; + else + *lenp = (int)STRLEN(s); } - - if (tv->v_type == VAR_PARTIAL) + else if (tv->v_type == VAR_PARTIAL) { partial_T *pt = tv->vval.v_partial; @@ -2439,6 +2458,7 @@ func_requires_g_prefix(ufunc_T *ufunc) { return func_is_global(ufunc) && (ufunc->uf_flags & FC_LAMBDA) == 0 + && !IS_GENERIC_FUNC(ufunc) && vim_strchr(ufunc->uf_name, AUTOLOAD_CHAR) == NULL && !SAFE_isdigit(ufunc->uf_name[0]); } @@ -2710,6 +2730,9 @@ func_clear_items(ufunc_T *fp) VIM_CLEAR(fp->uf_va_name); clear_func_type_list(&fp->uf_type_list, &fp->uf_func_type); + if (IS_GENERIC_FUNC(fp)) + generic_func_clear_items(fp); + // Increment the refcount of this function to avoid it being freed // recursively when the partial is freed. fp->uf_refcount += 3; @@ -3717,7 +3740,20 @@ func_call( if (item == NULL) { - funcexe_T funcexe; + funcexe_T funcexe; + int namelen = -1; + + if (in_vim9script()) + { + char_u *p = generic_func_find_open_bracket(name); + + if (p != NULL) + { + if (skip_generic_func_type_args(&p) == FAIL) + goto done; + namelen = p - name + 1; + } + } CLEAR_FIELD(funcexe); funcexe.fe_firstline = curwin->w_cursor.lnum; @@ -3731,13 +3767,14 @@ func_call( ++funcexe.fe_object->obj_refcount; } funcexe.fe_selfdict = selfdict; - r = call_func(name, -1, rettv, argc, argv, &funcexe); + r = call_func(name, namelen, rettv, argc, argv, &funcexe); } // Free the arguments. while (argc > 0) clear_tv(&argv[--argc]); +done: return r; } @@ -3784,7 +3821,8 @@ call_callback( ++funcexe.fe_object->obj_refcount; } ++callback_depth; - ret = call_func(callback->cb_name, len, rettv, argcount, argvars, &funcexe); + ret = call_func(callback->cb_name, len, rettv, argcount, argvars, + &funcexe); --callback_depth; // When a :def function was called that uses :try an error would be turned @@ -3926,11 +3964,14 @@ call_func( partial_T *partial = funcexe->fe_partial; type_T check_type; type_T *check_type_args[MAX_FUNC_ARGS]; + gfargs_tab_T gfatab; // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv) // even when call_func() returns FAIL. rettv->v_type = VAR_UNKNOWN; + generic_func_args_table_init(&gfatab); + if (partial != NULL) fp = partial->pt_func; if (fp == NULL) @@ -3938,6 +3979,19 @@ call_func( if (fp == NULL) { + if (in_vim9script()) + { + char_u *p = generic_func_find_open_bracket(funcname); + + if (p != NULL) + { + len = p - funcname; + if (parse_generic_func_type_args(funcname, len, p, &gfatab, + funcexe->fe_cctx) == NULL) + goto theend; + } + } + // Make a copy of the name, if it comes from a funcref variable it // could be changed or deleted in the called function. name = len > 0 ? vim_strnsave(funcname, len) : vim_strsave(funcname); @@ -4052,6 +4106,24 @@ call_func( fp = find_func(p, is_global); } + if (partial == NULL && fp != NULL && IS_GENERIC_FUNC(fp)) + { + // generic function call + fp = generic_func_get(fp, &gfatab); + if (fp == NULL) + { + error = FCERR_FAILED; + goto theend; + } + } + else if (generic_func_args_table_size(&gfatab) > 0) + { + emsg_funcname(fp != NULL ? e_not_a_generic_function_str + : e_unknown_generic_function_str, rfname); + error = FCERR_FAILED; + goto theend; + } + if (fp != NULL && (fp->uf_flags & FC_DELETED)) error = FCERR_DELETED; else if (fp != NULL) @@ -4087,6 +4159,7 @@ call_func( if (need_arg_check) error = may_check_argument_types(funcexe, argvars, argcount, TRUE, (name != NULL) ? name : funcname); + if (error == FCERR_NONE || error == FCERR_UNKNOWN) error = call_user_func_check(fp, argcount, argvars, rettv, funcexe, selfdict); @@ -4139,6 +4212,7 @@ theend: vim_free(tofree); vim_free(name); + generic_func_args_table_clear(&gfatab); return ret; } @@ -4966,7 +5040,8 @@ define_function( garray_T *lines_to_free, int class_flags, ocmember_T *obj_members, - int obj_member_count) + int obj_member_count, + cctx_T *cctx) { int j; int saved_did_emsg = FALSE; @@ -4982,6 +5057,7 @@ define_function( garray_T arg_objm; garray_T default_args; garray_T newlines; + gfargs_tab_T gfatab; int varargs = FALSE; int flags = 0; char_u *ret_type = NULL; @@ -5021,6 +5097,7 @@ define_function( ga_init(&argtypes); ga_init(&arg_objm); ga_init(&default_args); + generic_func_args_table_init(&gfatab); /* * Get the function name. There are these situations: @@ -5061,6 +5138,7 @@ define_function( eap->arg); return NULL; } + p = eap->arg; } @@ -5122,6 +5200,18 @@ define_function( goto ret_free; } + /* + * :function func() is a generic function + */ + p = skipwhite(p); + if (vim9script && eap->cmdidx == CMD_def && *p == '<') + { + // generic function + p = parse_generic_func_type_params(name, p, &gfatab, cctx); + if (p == NULL) + goto ret_free; + } + /* * ":function name(arg1, arg2)" Define function. */ @@ -5551,6 +5641,13 @@ define_function( fp->uf_def_status = UF_TO_BE_COMPILED; + if (generic_func_args_table_size(&gfatab) > 0) + { + // initialize generic function state + flags |= FC_GENERIC; + generic_func_init(fp, &gfatab); + } + // error messages are for the first function line SOURCING_LNUM = sourcing_lnum_top; @@ -5565,7 +5662,7 @@ define_function( is_export = FALSE; if (parse_argument_types(fp, &argtypes, varargs, &arg_objm, - obj_members, obj_member_count) == FAIL) + obj_members, obj_member_count, cctx) == FAIL) { SOURCING_LNUM = lnum_save; free_fp = fp_allocated; @@ -5575,7 +5672,7 @@ define_function( varargs = FALSE; // parse the return type, if any - if (parse_return_type(fp, ret_type) == FAIL) + if (parse_return_type(fp, ret_type, cctx) == FAIL) { SOURCING_LNUM = lnum_save; free_fp = fp_allocated; @@ -5674,6 +5771,10 @@ errret_keep: ret_free: ga_clear_strings(&argtypes); ga_clear(&arg_objm); + // The generic types are still in use and should not be freed. Instead + // clear the grow array. + ga_clear(&gfatab.gfat_param_types); + generic_func_args_table_clear(&gfatab); vim_free(fudi.fd_newkey); if (name != name_arg) vim_free(name); @@ -5692,7 +5793,7 @@ ex_function(exarg_T *eap) garray_T lines_to_free; ga_init2(&lines_to_free, sizeof(char_u *), 50); - (void)define_function(eap, NULL, &lines_to_free, 0, NULL, 0); + (void)define_function(eap, NULL, &lines_to_free, 0, NULL, 0, NULL); ga_clear_strings(&lines_to_free); } @@ -5750,6 +5851,8 @@ find_func_by_name(char_u *name, compiletype_T *compile_type) char_u *fname; ufunc_T *ufunc; int is_global = FALSE; + char_u *bracket_start = NULL; + char_u *generic_func_name = name; if (STRNCMP(arg, "profile", 7) == 0 && VIM_ISWHITE(arg[7])) { @@ -5784,6 +5887,16 @@ find_func_by_name(char_u *name, compiletype_T *compile_type) vim_free(fname); return ufunc; } + if (fname != NULL && *arg == '<') + { + generic_func_name = name; + bracket_start = arg; + if (skip_generic_func_type_args(&arg) == FAIL) + { + vim_free(fname); + return NULL; + } + } } if (fname == NULL) { @@ -5805,8 +5918,31 @@ find_func_by_name(char_u *name, compiletype_T *compile_type) if (p != NULL) // Try again without making it script-local. ufunc = find_func(p, FALSE); + + if (ufunc == NULL) + { + bracket_start = generic_func_find_open_bracket(fname); + if (bracket_start != NULL) + { + generic_func_name = fname; + *bracket_start = NUL; + ufunc = find_func(fname, FALSE); + *bracket_start = '<'; + } + } } + + if (ufunc != NULL) + { + // handle generic functions + if (bracket_start == NULL) + bracket_start = generic_func_name + STRLEN(generic_func_name); + + ufunc = eval_generic_func(ufunc, generic_func_name, &bracket_start); + } + vim_free(fname); + if (ufunc == NULL) semsg(_(e_cannot_find_function_str), name); return ufunc; @@ -6044,14 +6180,18 @@ get_user_func_name(expand_T *xp, int idx) * Returns NULL when out of memory. */ ufunc_T * -copy_function(ufunc_T *fp) +copy_function(ufunc_T *fp, int extra_namelen) { - ufunc_T *ufunc = alloc_ufunc(fp->uf_name, fp->uf_namelen); + ufunc_T *ufunc = alloc_ufunc(fp->uf_name, fp->uf_namelen + extra_namelen); if (ufunc == NULL) return NULL; // Most things can just be copied. + // The call to alloc_ufunc() above allocates a new uf_name_exp. So save + // and restore it. + char_u *save_uf_name_exp = ufunc->uf_name_exp; *ufunc = *fp; + ufunc->uf_name_exp = save_uf_name_exp; ufunc->uf_def_status = UF_TO_BE_COMPILED; ufunc->uf_dfunc_idx = 0; @@ -6082,6 +6222,9 @@ copy_function(ufunc_T *fp) // TODO: partial_T *uf_partial; + // copy generic function related state + copy_generic_function(fp, ufunc); + if (ufunc->uf_va_name != NULL) ufunc->uf_va_name = vim_strsave(ufunc->uf_va_name); @@ -6686,6 +6829,16 @@ ex_call(exarg_T *eap) vim9script && type == NULL ? &type : NULL, FALSE, FALSE, &found_var); + if (*arg == '<') + { + // generic function call + name = append_generic_func_type_args(name, STRLEN(name), &arg); + if (name == NULL) + goto end; + vim_free(tofree); + tofree = name; + } + // Skip white space to allow ":call func ()". Not good, but required for // backward compatibility. startarg = skipwhite(arg); diff --git a/src/version.c b/src/version.c index 11357b22e0..753be3bcc4 100644 --- a/src/version.c +++ b/src/version.c @@ -719,6 +719,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1577, /**/ 1576, /**/ diff --git a/src/vim9class.c b/src/vim9class.c index 991ebbce1c..aa68a6dc8a 100644 --- a/src/vim9class.c +++ b/src/vim9class.c @@ -101,7 +101,7 @@ parse_member( return FAIL; } type_arg = skipwhite(colon + 1); - type = parse_type(&type_arg, type_list, TRUE); + type = parse_type(&type_arg, type_list, NULL, NULL, TRUE); if (type == NULL) return FAIL; *has_type = TRUE; @@ -358,6 +358,32 @@ validate_extends_class( return success; } +/* + * Check a generic method is extended by a generic method and the number of + * type variables match. + */ + static int +validate_extends_generic_method( + class_T *super_cl, + ufunc_T *super_fp, + ufunc_T *cl_fp) +{ + char *msg = NULL; + + if (cl_fp->uf_generic_argcount == super_fp->uf_generic_argcount) + return TRUE; + + if (super_fp->uf_generic_argcount == 0) + msg = e_concrete_method_str_override_with_generic_method_in_class_str; + else if (cl_fp->uf_generic_argcount == 0) + msg = e_generic_method_str_override_with_concrete_method_in_class_str; + else + msg = e_generic_method_str_type_arguments_mismatch_in_class_str; + + semsg(_(msg), cl_fp->uf_name, super_cl->class_name); + return FALSE; +} + /* * Check method names in the parent class lineage to make sure the access is * the same for overridden methods. @@ -397,13 +423,20 @@ validate_extends_methods( int priv_method = (*qstr == '_'); if (priv_method) qstr++; - if (STRCMP(pstr, qstr) == 0 && priv_method != extends_private) + if (STRCMP(pstr, qstr) == 0) { - // Method access is different between the super class and - // the subclass - semsg(_(e_method_str_of_class_str_has_different_access), - cl_fp[j]->uf_name, super->class_name); - return FALSE; + if (priv_method != extends_private) + { + // Method access is different between the super class + // and the subclass + semsg(_(e_method_str_of_class_str_has_different_access), + cl_fp[j]->uf_name, super->class_name); + return FALSE; + } + + if (!validate_extends_generic_method(super, + extends_methods[i], cl_fp[j])) + return FALSE; } } } @@ -1436,7 +1469,8 @@ add_default_constructor( ga_init2(&lines_to_free, sizeof(char_u *), 50); ufunc_T *nf = define_function(&fea, NULL, &lines_to_free, CF_CLASS, - cl->class_obj_members, cl->class_obj_member_count); + cl->class_obj_members, cl->class_obj_member_count, + NULL); ga_clear_strings(&lines_to_free); vim_free(fga.ga_data); @@ -1519,7 +1553,7 @@ add_classfuncs_objmethods( // methods may be overruled, then "super.Method()" is used to // find a method from the parent. ufunc_T *pf = (extends_cl->class_obj_methods)[i]; - (*fup)[gap->ga_len + i] = copy_function(pf); + (*fup)[gap->ga_len + i] = copy_function(pf, 0); // If the child class overrides a function from the parent // the signature must be equal. @@ -1671,7 +1705,7 @@ enum_parse_values( p = skipwhite(eni_name_end); char_u *init_expr = NULL; - if (*p == '(') + if (*p == '(' || *p == '<') { if (VIM_ISWHITE(p[-1])) { @@ -1684,7 +1718,7 @@ enum_parse_values( p = eni_name_start; (void)skip_expr_concatenate(&p, &expr_start, &expr_end, &evalarg); - while (*expr_start && *expr_start != '(') + while (*expr_start && *expr_start != '(' && *expr_start != '<') expr_start++; if (expr_end > expr_start) @@ -2381,8 +2415,7 @@ early_ret: // enddef // static def ClassFunction() // enddef - // TODO: - // def someMethod() + // def someMethod() // enddef else if (checkforcmd(&p, "def", 3)) { @@ -2434,7 +2467,8 @@ early_ret: else class_flags = abstract_method ? CF_ABSTRACT_METHOD : CF_CLASS; ufunc_T *uf = define_function(&ea, NULL, &lines_to_free, - class_flags, objmembers.ga_data, objmembers.ga_len); + class_flags, objmembers.ga_data, objmembers.ga_len, + NULL); ga_clear_strings(&lines_to_free); if (uf != NULL) @@ -2842,7 +2876,7 @@ ex_type(exarg_T *eap) } scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); - type_T *type = parse_type(&arg, &si->sn_type_list, TRUE); + type_T *type = parse_type(&arg, &si->sn_type_list, NULL, NULL, TRUE); if (type == NULL) return; @@ -2958,6 +2992,7 @@ call_oc_method( char_u *name_end, evalarg_T *evalarg, char_u **arg, + gfargs_tab_T *gfatab, // generic types typval_T *rettv) { ufunc_T *fp; @@ -3006,6 +3041,14 @@ call_oc_method( return FAIL; } + // process generic function call + if (fp != NULL) + { + fp = generic_func_get(fp, gfatab); + if (fp == NULL) + return FAIL; + } + char_u *argp = name_end; int ret = get_func_arguments(&argp, evalarg, 0, argvars, &argcount, FALSE); if (ret == FAIL) @@ -3019,6 +3062,8 @@ call_oc_method( funcexe.fe_object = rettv->vval.v_object; ++funcexe.fe_object->obj_refcount; } + if (evalarg != NULL) + funcexe.fe_cctx = evalarg->eval_cctx; // Clear the class or object after calling the function, in // case the refcount is one. @@ -3091,6 +3136,8 @@ class_object_index( evalarg_T *evalarg, int verbose UNUSED) // give error messages { + int ret = FAIL; + if (VIM_ISWHITE((*arg)[1])) { semsg(_(e_no_white_space_allowed_after_str_str), ".", *arg); @@ -3124,10 +3171,28 @@ class_object_index( return FAIL; } + gfargs_tab_T gfatab; + + generic_func_args_table_init(&gfatab); + + if (*name_end == '<') + { + cctx_T *cctx = NULL; + + if (evalarg != NULL) + cctx = evalarg->eval_cctx; + + // calling a generic method + name_end = parse_generic_func_type_args(name, len, name + len, + &gfatab, cctx); + if (name_end == NULL) + goto done; + } + if (*name_end == '(') // Invoke the class or object method - return call_oc_method(cl, name, len, name_end, evalarg, arg, rettv); - + ret = call_oc_method(cl, name, len, name_end, evalarg, arg, + &gfatab, rettv); else if (rettv->v_type == VAR_OBJECT || rettv->v_type == VAR_CLASS) { // Search in the object member variable table and the class member @@ -3136,7 +3201,8 @@ class_object_index( if (get_member_tv(cl, is_object, name, len, NULL, rettv) == OK) { *arg = name_end; - return OK; + ret = OK; + goto done; } // could be a class method or an object method @@ -3148,22 +3214,26 @@ class_object_index( if (*name == '_') { semsg(_(e_cannot_access_protected_method_str), fp->uf_name); - return FAIL; + goto done; } if (obj_method_to_partial_tv(is_object ? rettv->vval.v_object : NULL, fp, rettv) == FAIL) - return FAIL; + goto done; *arg = name_end; - return OK; + ret = OK; + goto done; } if (did_emsg == did_emsg_save) member_not_found_msg(cl, rettv->v_type, name, len); } - return FAIL; +done: + generic_func_args_table_clear(&gfatab); + + return ret; } /* @@ -3200,6 +3270,13 @@ find_class_func(char_u **arg) fp = method_lookup(cl, tv.v_type, fname, len, NULL); + if (fp != NULL) + { + fp = eval_generic_func(fp, fname, &fname_end); + if (fp != NULL) + *arg = fname_end; + } + fail_after_eval: clear_tv(&tv); return fp; diff --git a/src/vim9cmds.c b/src/vim9cmds.c index d4ffb93313..4486213adf 100644 --- a/src/vim9cmds.c +++ b/src/vim9cmds.c @@ -292,7 +292,7 @@ compile_lock_unlock( #ifdef LOG_LOCKVAR ch_log(NULL, "LKVAR: ... INS_LOCKUNLOCK %s", name); #endif - if (compile_load(&name, name + len, cctx, FALSE, FALSE) == FAIL) + if (compile_load(&name, len, name + len, cctx, FALSE, FALSE) == FAIL) return FAIL; isn = ISN_LOCKUNLOCK; } @@ -1130,7 +1130,8 @@ compile_for(char_u *arg_start, cctx_T *cctx) goto failed; } p = skipwhite(p + 1); - lhs_type = parse_type(&p, cctx->ctx_type_list, TRUE); + lhs_type = parse_type(&p, cctx->ctx_type_list, cctx->ctx_ufunc, + cctx, TRUE); if (lhs_type == NULL) goto failed; } diff --git a/src/vim9compile.c b/src/vim9compile.c index a7c412e51d..8b7064bd7b 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1032,6 +1032,7 @@ compile_nested_function(exarg_T *eap, cctx_T *cctx, garray_T *lines_to_free) compiletype_T compile_type; int funcref_isn_idx = -1; lvar_T *lvar = NULL; + char_u *bracket_start = NULL; if (eap->forceit) { @@ -1047,6 +1048,14 @@ compile_nested_function(exarg_T *eap, cctx_T *cctx, garray_T *lines_to_free) ++name_end; set_nextcmd(eap, name_end); } + + if (*name_end == '<') + { + bracket_start = name_end; + if (skip_generic_func_type_args(&name_end) == FAIL) + return NULL; + } + if (name_end == name_start || *skipwhite(name_end) != '(') { if (!ends_excmd2(name_start, name_end)) @@ -1065,6 +1074,11 @@ compile_nested_function(exarg_T *eap, cctx_T *cctx, garray_T *lines_to_free) return eap->nextcmd == NULL ? (char_u *)"" : eap->nextcmd; } + if (bracket_start != NULL) + // generic function. The function name ends before the list of types + // (opening angle bracket). + name_end = bracket_start; + // Only g:Func() can use a namespace. if (name_start[1] == ':' && !is_global) { @@ -1104,7 +1118,8 @@ compile_nested_function(exarg_T *eap, cctx_T *cctx, garray_T *lines_to_free) int save_KeyTyped = KeyTyped; KeyTyped = FALSE; - ufunc = define_function(eap, lambda_name.string, lines_to_free, 0, NULL, 0); + ufunc = define_function(eap, lambda_name.string, lines_to_free, 0, NULL, 0, + cctx); KeyTyped = save_KeyTyped; @@ -2105,7 +2120,7 @@ compile_lhs_set_type(cctx_T *cctx, lhs_T *lhs, char_u *var_end, int is_decl) } p = skipwhite(var_end + 1); - lhs->lhs_type = parse_type(&p, cctx->ctx_type_list, TRUE); + lhs->lhs_type = parse_type(&p, cctx->ctx_type_list, cctx->ctx_ufunc, cctx, TRUE); if (lhs->lhs_type == NULL) return FAIL; diff --git a/src/vim9execute.c b/src/vim9execute.c index 55f9d43275..9d5709e257 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -1522,6 +1522,14 @@ call_by_name( return call_bfunc(func_idx, argcount, ectx); } + char_u cc = NUL; + char_u *start_bracket = generic_func_find_open_bracket(name); + if (start_bracket != NULL) + { + cc = *start_bracket; + *start_bracket = NUL; + } + ufunc = find_func(name, FALSE); if (ufunc == NULL) @@ -1533,11 +1541,36 @@ call_by_name( ufunc = find_func(name, FALSE); if (vim9_aborting(prev_uncaught_emsg)) + { + if (start_bracket != NULL) + *start_bracket = cc; return FAIL; // bail out if loading the script caused an error + } } + if (start_bracket != NULL) + *start_bracket = cc; + if (ufunc != NULL) { + if (IS_GENERIC_FUNC(ufunc)) + { + if (start_bracket != NULL) + ufunc = find_generic_func(ufunc, name, &start_bracket); + else + { + emsg_funcname(e_generic_func_missing_type_args_str, name); + ufunc = NULL; + } + if (ufunc == NULL) + return FAIL; + } + else if (start_bracket != NULL) + { + emsg_funcname(e_not_a_generic_function_str, name); + return FAIL; + } + if (check_ufunc_arg_types(ufunc, argcount, 0, ectx) == FAIL) return FAIL; @@ -5068,7 +5101,7 @@ exec_instructions(ectx_T *ectx) ea.cmd = ea.arg = iptr->isn_arg.string; ga_init2(&lines_to_free, sizeof(char_u *), 50); SOURCING_LNUM = iptr->isn_lnum; - define_function(&ea, NULL, &lines_to_free, 0, NULL, 0); + define_function(&ea, NULL, &lines_to_free, 0, NULL, 0, NULL); ga_clear_strings(&lines_to_free); } break; diff --git a/src/vim9expr.c b/src/vim9expr.c index 715771c7dc..f3aa4b1f5c 100644 --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -355,7 +355,8 @@ inside_class_hierarchy(cctx_T *cctx_arg, class_T *cl) static int compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) { - int m_idx; + int m_idx; + int ret = FAIL; if (VIM_ISWHITE((*arg)[1])) { @@ -410,6 +411,19 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) } size_t len = name_end - name; + gfargs_tab_T gfatab; + + generic_func_args_table_init(&gfatab); + + if (*name_end == '<') + { + // generic method call + name_end = parse_generic_func_type_args(name, len, name_end, + &gfatab, cctx); + if (name_end == NULL) + return FAIL; + } + if (*name_end == '(') { int function_count; @@ -458,14 +472,14 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) if (ocm == NULL || ocm->ocm_type->tt_type != VAR_FUNC) { method_not_found_msg(cl, type->tt_type, name, len); - return FAIL; + goto done; } if (type->tt_type == VAR_CLASS) { // Remove the class type from the stack --cctx->ctx_type_stack.ga_len; if (generate_CLASSMEMBER(cctx, TRUE, cl, m_idx) == FAIL) - return FAIL; + goto done; } else { @@ -478,7 +492,7 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) status = generate_GET_OBJ_MEMBER(cctx, m_idx, ocm->ocm_type); if (status == FAIL) - return FAIL; + goto done; } } @@ -488,7 +502,7 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) // allowed. semsg(_(e_abstract_method_str_direct), ufunc->uf_name, ufunc->uf_defclass->class_name); - return FAIL; + goto done; } // A private object method can be used only inside the class where it @@ -502,7 +516,15 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) && cctx->ctx_ufunc->uf_class != cl))) { semsg(_(e_cannot_access_protected_method_str), name); - return FAIL; + goto done; + } + + // process generic function call + if (ufunc != NULL) + { + ufunc = generic_func_get(ufunc, &gfatab); + if (ufunc == NULL) + goto done; } // Compile the arguments and call the class function or object method. @@ -511,14 +533,19 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) *arg = skipwhite(name_end + 1); int argcount = 0; if (compile_arguments(arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL) - return FAIL; + goto done; if (ocm != NULL) - return generate_PCALL(cctx, argcount, name, ocm->ocm_type, TRUE); + { + ret = generate_PCALL(cctx, argcount, name, ocm->ocm_type, TRUE); + goto done; + } if (type->tt_type == VAR_OBJECT && (cl->class_flags & (CLASS_INTERFACE | CLASS_EXTENDED))) - return generate_CALL(cctx, ufunc, cl, fi, argcount, is_super); - return generate_CALL(cctx, ufunc, NULL, 0, argcount, FALSE); + ret = generate_CALL(cctx, ufunc, cl, fi, argcount, is_super); + else + ret = generate_CALL(cctx, ufunc, NULL, 0, argcount, FALSE); + goto done; } if (type->tt_type == VAR_OBJECT) @@ -530,13 +557,15 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) { emsg_var_cl_define(e_cannot_access_protected_variable_str, m->ocm_name, 0, cl); - return FAIL; + goto done; } *arg = name_end; if (cl->class_flags & (CLASS_INTERFACE | CLASS_EXTENDED)) - return generate_GET_ITF_MEMBER(cctx, cl, m_idx, m->ocm_type); - return generate_GET_OBJ_MEMBER(cctx, m_idx, m->ocm_type); + ret = generate_GET_ITF_MEMBER(cctx, cl, m_idx, m->ocm_type); + else + ret = generate_GET_OBJ_MEMBER(cctx, m_idx, m->ocm_type); + goto done; } // Could be an object method reference: "obj.Func". @@ -548,12 +577,13 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) if (*name == '_' && !inside_class(cctx, cl)) { semsg(_(e_cannot_access_protected_method_str), fp->uf_name); - return FAIL; + goto done; } *arg = name_end; // Remove the object type from the stack --cctx->ctx_type_stack.ga_len; - return generate_FUNCREF(cctx, fp, cl, TRUE, m_idx, NULL); + ret = generate_FUNCREF(cctx, fp, cl, TRUE, m_idx, NULL); + goto done; } member_not_found_msg(cl, VAR_OBJECT, name, len); @@ -572,13 +602,14 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) { emsg_var_cl_define(e_cannot_access_protected_variable_str, m->ocm_name, 0, cl); - return FAIL; + goto done; } *arg = name_end; // Remove the class type from the stack --cctx->ctx_type_stack.ga_len; - return generate_CLASSMEMBER(cctx, TRUE, cl, idx); + ret = generate_CLASSMEMBER(cctx, TRUE, cl, idx); + goto done; } // Could be a class method reference: "class.Func". @@ -590,18 +621,21 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) if (*name == '_' && !inside_class(cctx, cl)) { semsg(_(e_cannot_access_protected_method_str), fp->uf_name); - return FAIL; + goto done; } *arg = name_end; // Remove the class type from the stack --cctx->ctx_type_stack.ga_len; - return generate_FUNCREF(cctx, fp, cl, FALSE, m_idx, NULL); + ret = generate_FUNCREF(cctx, fp, cl, FALSE, m_idx, NULL); + goto done; } member_not_found_msg(cl, VAR_CLASS, name, len); } - return FAIL; +done: + generic_func_args_table_clear(&gfatab); + return ret; } /* @@ -731,6 +765,43 @@ compile_load_scriptvar( { if (ufunc != NULL) { + gfargs_tab_T gfatab; + + generic_func_args_table_init(&gfatab); + + if (IS_GENERIC_FUNC(ufunc)) + { + if (*p == '<') + { + // generic function call + p = parse_generic_func_type_args(name, STRLEN(name), p, + &gfatab, cctx); + if (p != NULL) + { + *end = p; + + // generic function call + ufunc = generic_func_get(ufunc, &gfatab); + } + + generic_func_args_table_clear(&gfatab); + + if (p == NULL || ufunc == NULL) + return FAIL; + } + else + { + emsg_funcname(e_generic_func_missing_type_args_str, + name); + return FAIL; + } + } + else if (*p == '<') + { + emsg_funcname(e_not_a_generic_function_str, name); + return FAIL; + } + // function call or function reference generate_PUSHFUNC(cctx, ufunc->uf_name, NULL, TRUE); return OK; @@ -752,7 +823,11 @@ compile_load_scriptvar( } static int -generate_funcref(cctx_T *cctx, char_u *name, int has_g_prefix) +generate_funcref( + cctx_T *cctx, + char_u *name, + gfargs_tab_T *gfatab, + int has_g_prefix) { ufunc_T *ufunc = find_func(name, FALSE); compiletype_T compile_type; @@ -761,6 +836,11 @@ generate_funcref(cctx_T *cctx, char_u *name, int has_g_prefix) if (ufunc == NULL || (!has_g_prefix && func_requires_g_prefix(ufunc))) return FAIL; + // process generic function call + ufunc = generic_func_get(ufunc, gfatab); + if (ufunc == NULL) + return FAIL; + // Need to compile any default values to get the argument types. compile_type = get_compile_type(ufunc); if (func_needs_compiling(ufunc, compile_type) @@ -798,6 +878,7 @@ compiling_a_class_method(cctx_T *cctx) int compile_load( char_u **arg, + size_t namelen, char_u *end_arg, cctx_T *cctx, int is_expr, @@ -808,6 +889,18 @@ compile_load( char_u *end = end_arg; int res = FAIL; int prev_called_emsg = called_emsg; + gfargs_tab_T gfatab; + + generic_func_args_table_init(&gfatab); + + if (*(*arg + namelen) == '<') + { + // generic function call + if (parse_generic_func_type_args(*arg, namelen, + *arg + namelen, &gfatab, cctx) == NULL) + return FAIL; + *(*arg + namelen) = NUL; + } if (*(*arg + 1) == ':') { @@ -837,7 +930,7 @@ compile_load( // load namespaced variable name = vim_strnsave(*arg + 2, end - (*arg + 2)); if (name == NULL) - return FAIL; + goto theend; switch (**arg) { @@ -847,11 +940,10 @@ compile_load( { semsg(_(e_cannot_use_s_colon_in_vim9_script_str), *arg); - vim_free(name); - return FAIL; + goto theend; } if (is_expr && find_func(name, FALSE) != NULL) - res = generate_funcref(cctx, name, FALSE); + res = generate_funcref(cctx, name, &gfatab, FALSE); else res = compile_load_scriptvar(cctx, name, NULL, &end, NULL); @@ -859,8 +951,10 @@ compile_load( case 'g': if (vim_strchr(name, AUTOLOAD_CHAR) == NULL) { if (is_expr && ASCII_ISUPPER(*name) - && find_func(name, FALSE) != NULL) - res = generate_funcref(cctx, name, TRUE); + && (find_func(name, FALSE) != NULL + || gfatab.gfat_args.ga_len > 0)) + res = generate_funcref(cctx, name, &gfatab, + TRUE); else isn_type = ISN_LOADG; } @@ -870,7 +964,7 @@ compile_load( vim_free(name); name = vim_strnsave(*arg, end - *arg); if (name == NULL) - return FAIL; + goto theend; } break; case 'w': isn_type = ISN_LOADW; break; @@ -891,7 +985,6 @@ compile_load( } else { - size_t len = end - *arg; int idx; int method_idx; int gen_load = FALSE; @@ -899,9 +992,9 @@ compile_load( int outer_loop_depth = -1; int outer_loop_idx = -1; - name = vim_strnsave(*arg, len); + name = vim_strnsave(*arg, namelen); if (name == NULL) - return FAIL; + goto theend; if (STRCMP(name, "super") == 0 && compiling_a_class_method(cctx)) { @@ -916,7 +1009,7 @@ compile_load( script_autoload(name, FALSE); res = generate_LOAD(cctx, ISN_LOADAUTO, 0, name, &t_any); } - else if (arg_exists(*arg, len, &idx, &type, &gen_load_outer, cctx) + else if (arg_exists(*arg, namelen, &idx, &type, &gen_load_outer, cctx) == OK) { if (gen_load_outer == 0) @@ -927,7 +1020,7 @@ compile_load( lvar_T lvar; class_T *cl = NULL; - if (lookup_local(*arg, len, &lvar, cctx) == OK) + if (lookup_local(*arg, namelen, &lvar, cctx) == OK) { type = lvar.lv_type; idx = lvar.lv_idx; @@ -942,9 +1035,9 @@ compile_load( } else if (cctx->ctx_ufunc->uf_defclass != NULL && (((idx = - cctx_class_member_idx(cctx, *arg, len, &cl)) >= 0) + cctx_class_member_idx(cctx, *arg, namelen, &cl)) >= 0) || ((method_idx = - cctx_class_method_idx(cctx, *arg, len, &cl)) >= 0))) + cctx_class_method_idx(cctx, *arg, namelen, &cl)) >= 0))) { // Referencing a class variable or method without the class // name. A class variable or method can be referenced without @@ -974,7 +1067,7 @@ compile_load( // "var" can be script-local even without using "s:" if it // already exists in a Vim9 script or when it's imported. - if (script_var_exists(*arg, len, cctx, NULL) == OK + if (script_var_exists(*arg, namelen, cctx, NULL) == OK || (imp = find_imported(name, 0, FALSE)) != NULL) res = compile_load_scriptvar(cctx, name, *arg, &end, imp); @@ -982,7 +1075,7 @@ compile_load( // uppercase letter it can be a user defined function. // generate_funcref() will fail if the function can't be found. if (res == FAIL && is_expr && ASCII_ISUPPER(*name)) - res = generate_funcref(cctx, name, FALSE); + res = generate_funcref(cctx, name, &gfatab, FALSE); } } if (gen_load) @@ -1001,6 +1094,7 @@ theend: if (res == FAIL && error && called_emsg == prev_called_emsg) semsg(_(e_variable_not_found_str), name); vim_free(name); + generic_func_args_table_clear(&gfatab); return res; } @@ -1123,13 +1217,16 @@ compile_arguments( if (*p != ',' && *skipwhite(p) == ',') { semsg(_(e_no_white_space_allowed_before_str_str), ",", p); - p = skipwhite(p); + return FAIL; } if (*p == ',') { ++p; if (*p != NUL && !VIM_ISWHITE(*p)) + { semsg(_(e_white_space_required_after_str_str), ",", p - 1); + return FAIL; + } } else must_end = TRUE; @@ -1280,7 +1377,23 @@ compile_call( else special_fn = CA_NOT_SPECIAL; - *arg = skipwhite(*arg + varlen + 1); + gfargs_tab_T gfatab; + + generic_func_args_table_init(&gfatab); + + if (*(*arg + varlen) == '<') + { + // generic function + *arg = parse_generic_func_type_args(*arg, varlen, *arg + varlen, + &gfatab, cctx); + if (*arg == NULL) + goto theend; + ++*arg; // skip '(' + } + else + *arg += varlen + 1; + + *arg = skipwhite(*arg); if (compile_arguments(arg, cctx, &argcount, special_fn) == FAIL) goto theend; @@ -1366,15 +1479,22 @@ compile_call( ufunc = find_func(name, FALSE); if (ufunc != NULL) { + // process generic function call + ufunc = generic_func_get(ufunc, &gfatab); + if (ufunc == NULL) + goto theend; + if (!func_is_global(ufunc)) { res = generate_CALL(cctx, ufunc, NULL, 0, argcount, FALSE); goto theend; } if (!has_g_namespace - && vim_strchr(ufunc->uf_name, AUTOLOAD_CHAR) == NULL) + && vim_strchr(ufunc->uf_name, AUTOLOAD_CHAR) == NULL + && !IS_GENERIC_FUNC(ufunc)) { - // A function name without g: prefix must be found locally. + // A function name without g: prefix must be found locally + // A generic function has the name emptied out. emsg_funcname(e_unknown_function_str, namebuf); goto theend; } @@ -1404,7 +1524,7 @@ compile_call( // Not for some#Func(), it will be loaded later. p = namebuf; if (!has_g_namespace && !is_autoload - && compile_load(&p, namebuf + varlen, cctx, FALSE, FALSE) == OK) + && compile_load(&p, varlen, namebuf + varlen, cctx, FALSE, FALSE) == OK) { type_T *s_type = get_type_on_stack(cctx, 0); @@ -1427,6 +1547,7 @@ compile_call( emsg_funcname(e_unknown_function_str, namebuf); theend: + generic_func_args_table_clear(&gfatab); vim_free(tofree); return res; } @@ -1640,7 +1761,7 @@ compile_lambda(char_u **arg, cctx_T *cctx) evalarg.eval_cctx = cctx; // Get the funcref in "rettv". - r = get_lambda_tv(arg, &rettv, TRUE, &evalarg); + r = get_lambda_tv(arg, &rettv, TRUE, &evalarg, cctx); if (r != OK) { clear_evalarg(&evalarg, NULL); @@ -1730,7 +1851,7 @@ get_lambda_tv_and_compile( // Get the funcref in "rettv". current_sctx.sc_version = SCRIPT_VERSION_VIM9; - r = get_lambda_tv(arg, rettv, types_optional, evalarg); + r = get_lambda_tv(arg, rettv, types_optional, evalarg, NULL); current_sctx.sc_version = save_sc_version; if (r != OK) return r; // currently unreachable @@ -2994,16 +3115,23 @@ compile_expr9( return FAIL; } + size_t namelen = p - *arg; + if (*p == '<') + { + if (skip_generic_func_type_args(&p) == FAIL) + return FAIL; + } + if (*p == '(') { - r = compile_call(arg, p - *arg, cctx, ppconst, 0); + r = compile_call(arg, namelen, cctx, ppconst, 0); } else { if (cctx->ctx_skip != SKIP_YES && generate_ppconst(cctx, ppconst) == FAIL) return FAIL; - r = compile_load(arg, p, cctx, TRUE, TRUE); + r = compile_load(arg, namelen, p, cctx, TRUE, TRUE); } if (r == FAIL) return FAIL; @@ -3039,7 +3167,7 @@ compile_expr8(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) if (**arg == '<' && eval_isnamec1((*arg)[1])) { ++*arg; - want_type = parse_type(arg, cctx->ctx_type_list, TRUE); + want_type = parse_type(arg, cctx->ctx_type_list, cctx->ctx_ufunc, cctx, TRUE); if (want_type == NULL) return FAIL; diff --git a/src/vim9generics.c b/src/vim9generics.c new file mode 100644 index 0000000000..1452610a3f --- /dev/null +++ b/src/vim9generics.c @@ -0,0 +1,1285 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * vim9generics.c: Vim9 script generics support + */ + +#include "vim.h" + +#if defined(FEAT_EVAL) || defined(PROTO) + + +/* + * A hash table is used to lookup a generic function with specific types. + * The specific type names are used as the key. + */ +typedef struct gfitem_S gfitem_T; +struct gfitem_S +{ + ufunc_T *gfi_ufunc; + char_u gfi_name[1]; // actually longer +}; +#define GFITEM_KEY_OFF offsetof(gfitem_T, gfi_name) +#define HI2GFITEM(hi) ((gfitem_T *)((hi)->hi_key - GFITEM_KEY_OFF)) + +static type_T *find_generic_type_in_cctx(char_u *gt_name, size_t len, cctx_T *cctx); + +/* + * Returns a pointer to the first '<' character in "name" that starts the + * generic type argument list, skipping an initial or prefix if + * present. The prefix is only skipped if "name" starts with '<'. + * + * Returns NULL if no '<' is found before a '(' or the end of the string. + * The returned pointer refers to the original string. + * + * Examples: + * "123_Fn" -> returns pointer to '<' + * "123_Fn" -> returns pointer to '<' + * "Func" -> returns pointer to '<' + * "Func()" -> returns NULL + */ + char_u * +generic_func_find_open_bracket(char_u *name) +{ + char_u *p = name; + + if (name[0] == '<') + { + // Skip the or at the start of the name + if (STRNCMP(name + 1, "SNR>", 4) == 0) + p += 5; + else if (STRNCMP(name + 1, "lambda>", 7) == 0) + p += 8; + } + + while (*p && *p != '(' && *p != '<') + p++; + + if (*p == '<') + return p; + + return NULL; +} + +/* + * Finds the matching '>' character for a generic function type parameter or + * argument list, starting from the opening '<'. + * + * Enforces correct syntax for a flat, comma-separated list of types: + * - No whitespace before or after type names or commas + * - Each type must be non-empty and separated by a comma and whitespace + * - At least one type must be present + * + * Arguments: + * start - pointer to the opening '<' + * + * Returns: + * Pointer to the matching '>' character if found and syntax is valid, + * or NULL if not found, invalid syntax, or on error. + */ + static char_u * +generic_func_find_close_bracket(char_u *start) +{ + char_u *p = start + 1; + int type_count = 0; + + while (*p && *p != '>') + { + char_u *typename = p; + + if (VIM_ISWHITE(*p)) + { + char tmpstr[2]; + tmpstr[0] = *(p - 1); tmpstr[1] = NUL; + semsg(_(e_no_white_space_allowed_after_str_str), tmpstr, start); + return NULL; + } + + p = skip_type(p, FALSE); + if (p == typename) + { + char_u cc = *p; + *p = NUL; + semsg(_(e_missing_type_after_str), start); + *p = cc; + return NULL; + } + type_count++; + + if (*p == '>' || *p == NUL) + break; + + if (VIM_ISWHITE(*p)) + { + char_u cc = *p; + *p = NUL; + semsg(_(e_no_white_space_allowed_after_str_str), typename, start); + *p = cc; + return NULL; + } + + if (*p != ',') + { + semsg(_(e_missing_comma_in_generic_function_str), start); + return NULL; + } + p++; + + if (*p == NUL) + break; + + if (!VIM_ISWHITE(*p)) + { + semsg(_(e_white_space_required_after_str_str), ",", start); + return NULL; + } + p = skipwhite(p); + } + + if (*p != '>') + { + semsg(_(e_missing_closing_angle_bracket_in_generic_function_str), start); + return NULL; + } + + if (type_count == 0) + { + semsg(_(e_empty_type_list_for_generic_function_str), start); + return NULL; + } + + return p; +} + +/* + * Advances the argument pointer past a generic function's type argument list. + * + * On entry, "*argp" must point to the opening '<' of a generic type argument + * list. This function finds the matching closing '>' (validating the syntax + * via generic_func_find_close_bracket), and if successful, advances "*argp" to + * the character immediately after the closing '>'. + * + * Returns OK on success, or FAIL if the type argument list is invalid or no + * matching '>' is found. On failure, "*argp" is not modified. + */ + int +skip_generic_func_type_args(char_u **argp) +{ + char_u *p = generic_func_find_close_bracket(*argp); + if (p == NULL) + return FAIL; + + *argp = p + 1; // skip '>' + + return OK; +} + +/* + * Appends the generic function type arguments, starting at "*argp", to the + * function name "funcname" (of length "namelen") and returns a newly allocated + * string containing the result. + * + * On entry, "*argp" must point to the opening '<' of the generic type argument + * list. If the type argument list is valid, the substring from "*argp" up to + * and including the matching '>' is appended to "funcname". On success, + * "*argp" is updated to point to the character after the closing '>'. + * + * Returns: + * A newly allocated string with the combined function name and type + * arguments, or NULL if there is a syntax error in the generic type + * arguments. + * + * The caller is responsible for freeing the returned string. + */ + char_u * +append_generic_func_type_args( + char_u *funcname, + size_t namelen, + char_u **argp) +{ + char_u *p = generic_func_find_close_bracket(*argp); + + if (p == NULL) + return NULL; + + vim_strncpy(IObuff, funcname, namelen); + STRNCAT(IObuff, *argp, p - *argp + 1); + + *argp = p + 1; + + return vim_strsave(IObuff); +} + +/* + * Returns a newly allocated string containing the function name from "fp" with + * the generic type arguments from "*argp" appended. + * + * On entry, "*argp" must point to the opening '<' of the generic type argument + * list. On success, "*argp" is advanced to the character after the closing + * '>'. + * + * Returns: + * A newly allocated string with the combined function name and type + * arguments, or NULL if "fp" is not a generic function, if there is a + * parsing error, or on memory allocation failure. + * + * The caller is responsible for freeing the returned string. + */ + char_u * +get_generic_func_name(ufunc_T *fp, char_u **argp) +{ + if (!IS_GENERIC_FUNC(fp)) + { + emsg_funcname(e_not_a_generic_function_str, fp->uf_name); + return NULL; + } + + return append_generic_func_type_args(fp->uf_name, fp->uf_namelen, argp); +} + +/* + * Parses the concrete type arguments provided in a generic function call, + * starting at the opening '<' character and ending at the matching '>'. + * + * On entry, "start" must point to the opening '<' character. + * On success, returns a pointer to the character after the closing '>'. + * On failure, returns NULL and reports an error message. + * + * Arguments: + * func_name - the name of the function being called (used for error + * messages) + * namelen - length of the function name + * start - pointer to the opening '<' character in the call + * gfatab - args table to allocate new type objects and to store parsed + * type argument names and their types. + * cctx - compile context for type resolution (may be NULL) + * + * This function enforces correct syntax for generic type argument lists, + * including whitespace rules, comma separation, and non-empty argument lists. + */ + char_u * +parse_generic_func_type_args( + char_u *func_name, + size_t namelen, + char_u *start, + gfargs_tab_T *gfatab, + cctx_T *cctx) +{ + generic_T *generic_arg; + type_T *type_arg; + char_u *p = start; + + // White spaces not allowed after '<' + if (VIM_ISWHITE(*(p + 1))) + { + semsg(_(e_no_white_space_allowed_after_str_str), "<", p); + return NULL; + } + + ++p; // skip the '<' + + // parse each type argument until '>' or end of string + while (*p && *p != '>') + { + p = skipwhite(p); + + if (!ASCII_ISALNUM(*p)) + { + semsg(_(e_missing_type_after_str), start); + return NULL; + } + + // parse the type + type_arg = parse_type(&p, &gfatab->gfat_arg_types, NULL, cctx, TRUE); + if (type_arg == NULL) + return NULL; + + char *ret_free = NULL; + char *ret_name = type_name(type_arg, &ret_free); + + // create space for the name and the new type + if (ga_grow(&gfatab->gfat_args, 1) == FAIL) + return NULL; + generic_arg = (generic_T *)gfatab->gfat_args.ga_data + + gfatab->gfat_args.ga_len; + gfatab->gfat_args.ga_len++; + + // copy the type name + generic_arg->gt_name = alloc(STRLEN(ret_name) + 1); + if (generic_arg->gt_name == NULL) + return NULL; + STRCPY(generic_arg->gt_name, ret_name); + vim_free(ret_free); + + // add the new type + generic_arg->gt_type = type_arg; + + p = skipwhite(p); + + // after a type, expect ',' or '>' + if (*p != ',' && *p != '>') + { + semsg(_(e_missing_comma_in_generic_function_str), start); + return NULL; + } + + // if there's a comma, require whitespace after it and skip it + if (*p == ',') + { + if (!VIM_ISWHITE(*(p + 1))) + { + semsg(_(e_white_space_required_after_str_str), ",", p); + return NULL; + } + p++; + } + } + + // ensure the list of types ends in a closing '>' + if (*p != '>') + return NULL; + + // no whitespace allowed before '>' + if (VIM_ISWHITE(*(p - 1))) + { + semsg(_(e_no_white_space_allowed_before_str_str), ">", p); + return NULL; + } + + // at least one type argument is required + if (generic_func_args_table_size(gfatab) == 0) + { + char_u cc = func_name[namelen]; + func_name[namelen] = NUL; + semsg(_(e_empty_type_list_for_generic_function_str), func_name); + func_name[namelen] = cc; + return NULL; + } + ++p; // skip the '>' + + return p; +} + +/* + * Checks if a generic type name already exists in the current context. + * + * This function verifies that the given generic type name "name" does not + * conflict with an imported variable, an existing generic type in the provided + * growarray "gt_gap", or a generic type in the current or outer compile + * context "cctx". If a conflict is found, an appropriate error message is + * reported. + * + * Arguments: + * name - the generic type name to check + * gfatab - args table to allocate new type objects and to store parsed + * type argument names and their types. + * cctx - current compile context, used to check for outer generic types + * (may be NULL) + * + * Returns: + * TRUE if the name already exists or conflicts, FALSE otherwise. + */ + static int +generic_name_exists( + char_u *gt_name, + size_t name_len, + gfargs_tab_T *gfatab, + cctx_T *cctx) +{ + typval_T tv; + + tv.v_type = VAR_UNKNOWN; + + if (eval_variable_import(gt_name, &tv) == OK) + { + semsg(_(e_redefining_script_item_str), gt_name); + clear_tv(&tv); + return TRUE; + } + + for (int i = 0; i < gfatab->gfat_args.ga_len; i++) + { + generic_T *generic = &((generic_T *)gfatab->gfat_args.ga_data)[i]; + + if (STRNCMP(gt_name, generic->gt_name, name_len) == 0) + { + semsg(_(e_duplicate_type_var_name_str), gt_name); + return TRUE; + } + } + + if (cctx != NULL && + find_generic_type_in_cctx(gt_name, name_len, cctx) != NULL) + { + semsg(_(e_duplicate_type_var_name_str), gt_name); + return TRUE; + } + + return FALSE; +} + +/* + * Parses the type parameters specified when defining a new generic function, + * starting at the opening '<' character and ending at the matching '>'. + * + * On entry, "p" must point to the opening '<' character. + * On success, returns a pointer to the character after the closing '>'. + * On failure, returns NULL and reports an error message. + * + * Arguments: + * func_name - the name of the function being defined (for error messages) + * p - pointer to the opening '<' character in the definition + * gfatab - args table to allocate new type objects and to store parsed + * type argument names and their types. + * cctx - current compile context, used to check for duplicate names in + * outer scopes (may be NULL) + * + * This function enforces correct syntax for generic type parameter lists: + * - No whitespace before or after the opening '<' + * - Parameters must be separated by a comma and whitespace + * - No whitespace after a parameter name + * - The list must not be empty + */ + char_u * +parse_generic_func_type_params( + char_u *func_name, + char_u *p, + gfargs_tab_T *gfatab, + cctx_T *cctx) +{ + // No white space allowed before the '<' + if (VIM_ISWHITE(*(p - 1))) + { + semsg(_(e_no_white_space_allowed_before_str_str), "<", p); + return NULL; + } + + if (VIM_ISWHITE(*(p + 1))) + { + semsg(_(e_no_white_space_allowed_after_str_str), "<", p); + return NULL; + } + + char_u *start = ++p; + + while (*p && *p != '>') + { + p = skipwhite(p); + + if (*p == NUL || *p == '>') + { + semsg(_(e_missing_type_after_str), p - 1); + return NULL; + } + + if (!ASCII_ISUPPER(*p)) + { + if (ASCII_ISLOWER(*p)) + semsg(_(e_type_var_name_must_start_with_uppercase_letter_str), p); + else + semsg(_(e_missing_type_after_str), p - 1); + return NULL; + } + + char_u *name_start = p; + char_u *name_end = NULL; + char_u cc; + size_t name_len = 0; + + p++; + while (ASCII_ISALNUM(*p) || *p == '_') + p++; + name_end = p; + + name_len = name_end - name_start; + cc = *name_end; + *name_end = NUL; + + int name_exists = generic_name_exists(name_start, name_len, gfatab, + cctx); + *name_end = cc; + if (name_exists) + return NULL; + + if (ga_grow(&gfatab->gfat_param_types, 1) == FAIL) + return NULL; + type_T *gt = + &((type_T *)gfatab->gfat_param_types.ga_data)[gfatab->gfat_param_types.ga_len]; + gfatab->gfat_param_types.ga_len++; + + CLEAR_POINTER(gt); + gt->tt_type = VAR_ANY; + gt->tt_flags = TTFLAG_GENERIC; + + if (ga_grow(&gfatab->gfat_args, 1) == FAIL) + return NULL; + generic_T *generic = + &((generic_T *)gfatab->gfat_args.ga_data)[gfatab->gfat_args.ga_len]; + gfatab->gfat_args.ga_len++; + + generic->gt_name = alloc(name_len + 1); + if (generic->gt_name == NULL) + return NULL; + vim_strncpy(generic->gt_name, name_start, name_len); + generic->gt_type = gt; + + if (VIM_ISWHITE(*p)) + { + semsg(_(e_no_white_space_allowed_after_str_str), generic->gt_name, + name_start); + return NULL; + } + + if (*p != ',' && *p != '>') + { + semsg(_(e_missing_comma_in_generic_function_str), start); + return NULL; + } + if (*p == ',') + { + if (!VIM_ISWHITE(*(p + 1))) + { + semsg(_(e_white_space_required_after_str_str), ",", p); + return NULL; + } + p++; + } + } + if (*p != '>') + return NULL; + + if (generic_func_args_table_size(gfatab) == 0) + { + emsg_funcname(e_empty_type_list_for_generic_function_str, func_name); + return NULL; + } + p++; + + return p; +} + +/* + * Initialize a new generic function "fp" using the list of generic types and + * generic arguments in "gfatab". + * + * This function: + * - Marks the function as generic. + * - Sets the generic argument count and stores the type and argument lists. + * - Transfers ownership of the arrays from the growarrays to the function. + * - Initializes the generic function's lookup table. + */ + void +generic_func_init(ufunc_T *fp, gfargs_tab_T *gfatab) +{ + fp->uf_flags |= FC_GENERIC; + fp->uf_generic_argcount = gfatab->gfat_args.ga_len; + fp->uf_generic_args = (generic_T *)gfatab->gfat_args.ga_data; + ga_init(&gfatab->gfat_args); // remove the reference to the args + fp->uf_generic_param_types = (type_T *)gfatab->gfat_param_types.ga_data; + ga_init(&gfatab->gfat_param_types); // remove the reference to the types + ga_init(&fp->uf_generic_arg_types); + hash_init(&fp->uf_generic_functab); +} + +/* + * Initialize the generic function args table + */ + void +generic_func_args_table_init(gfargs_tab_T *gfatab) +{ + ga_init2(&gfatab->gfat_args, sizeof(generic_T), 10); + ga_init2(&gfatab->gfat_param_types, sizeof(type_T), 10); + ga_init2(&gfatab->gfat_arg_types, sizeof(type_T), 10); +} + +/* + * Return the number of entries in the generic function args table + */ + int +generic_func_args_table_size(gfargs_tab_T *gfatab) +{ + return gfatab->gfat_args.ga_len; +} + +/* + * Free all the generic function args table items + */ + void +generic_func_args_table_clear(gfargs_tab_T *gfatab) +{ + clear_type_list(&gfatab->gfat_param_types); + clear_type_list(&gfatab->gfat_arg_types); + for (int i = 0; i < gfatab->gfat_args.ga_len; i++) + { + generic_T *generic = &((generic_T *)gfatab->gfat_args.ga_data)[i]; + VIM_CLEAR(generic->gt_name); + } + ga_clear(&gfatab->gfat_args); +} + +/* + * When a cloning a function "fp" to "new_fp", copy the generic function + * related information. + */ + void +copy_generic_function(ufunc_T *fp, ufunc_T *new_fp) +{ + int i; + int sz; + + if (!IS_GENERIC_FUNC(fp)) + return; + + sz = fp->uf_generic_argcount * sizeof(type_T); + new_fp->uf_generic_param_types = alloc_clear(sz); + if (new_fp->uf_generic_param_types == NULL) + return; + + memcpy(new_fp->uf_generic_param_types, fp->uf_generic_param_types, sz); + + sz = fp->uf_generic_argcount * sizeof(generic_T); + new_fp->uf_generic_args = alloc_clear(sz); + if (new_fp->uf_generic_args == NULL) + { + VIM_CLEAR(new_fp->uf_generic_param_types); + return; + } + memcpy(new_fp->uf_generic_args, fp->uf_generic_args, sz); + + for (i = 0; i < fp->uf_generic_argcount; i++) + new_fp->uf_generic_args[i].gt_name = + vim_strsave(fp->uf_generic_args[i].gt_name); + + for (i = 0; i < fp->uf_generic_argcount; i++) + new_fp->uf_generic_args[i].gt_type = + &new_fp->uf_generic_param_types[i]; + + ga_init(&new_fp->uf_generic_arg_types); + hash_init(&new_fp->uf_generic_functab); +} + +/* + * Returns the index of the generic type pointer "t" in the generic type list + * of the function "fp". + * + * Arguments: + * fp - pointer to the generic function (ufunc_T) + * t - pointer to the type_T to search for in the function's generic type + * list + * + * Returns: + * The zero-based index of "t" in fp->uf_generic_param_types if found, + * or -1 if not found. + */ + static int +get_generic_type_index(ufunc_T *fp, type_T *t) +{ + for (int i = 0; i < fp->uf_generic_argcount; i++) + { + if (&fp->uf_generic_param_types[i] == t) + return i; + } + return -1; +} + +/* + * Evaluates the type arguments for a generic function call and looks up the + * corresponding concrete function. + * + * Arguments: + * ufunc - the original (possibly generic) function to evaluate + * name - the function name (used for error messages and lookup) + * argp - pointer to a pointer to the argument string; on entry, "*argp" + * should point to the character after the function name (possibly + * '<') + * + * Returns: + * The concrete function corresponding to the given type arguments, + * or NULL on error (with an error message reported). + * + * Behavior: + * - If "ufunc" is a generic function and "*argp" points to '<', attempts to + * find or instantiate the concrete function with the specified type + * arguments. On success, advances "*argp" past the type argument list. + * - If "ufunc" is generic but "*argp" does not point to '<', reports a + * missing type argument error. + * - If "ufunc" is not generic but "*argp" points to '<', reports an error + * that the function is not generic. + * - Otherwise, returns the original function. + */ + ufunc_T * +eval_generic_func( + ufunc_T *ufunc, + char_u *name, + char_u **argp) +{ + if (IS_GENERIC_FUNC(ufunc)) + { + if (**argp == '<') + ufunc = find_generic_func(ufunc, name, argp); + else + { + emsg_funcname(e_generic_func_missing_type_args_str, name); + return NULL; + } + } + else if (**argp == '<') + { + emsg_funcname(e_not_a_generic_function_str, name); + return NULL; + } + + return ufunc; +} + +/* + * Checks if the string at "*argp" represents a generic function call with type + * arguments, i.e., if it starts with a '<', contains a valid type argument + * list, a closing '>', and is immediately followed by '('. + * + * On entry, "*argp" should point to the '<' character. + * If the pattern matches, advances "*argp" to point to the '(' and returns + * TRUE. If not, leaves "*argp" unchanged and returns FALSE. + * + * Example: + * "(" + */ + int +generic_func_call(char_u **argp) +{ + char_u *p = *argp; + + if (*p != '<') + return FALSE; + + if (skip_generic_func_type_args(&p) == FAIL) + return FALSE; + + if (*p != '(') + return FALSE; + + *argp = p; + return TRUE; +} + +/* + * Recursively replaces all occurrences of the generic type "generic_type" in a + * type structure with the corresponding concrete type from "new_ufunc", based + * on the mapping from the original generic function "ufunc". + * + * This is used when instantiating a new function "new_ufunc" from a generic + * function "ufunc" with specific type arguments. The function updates all + * relevant type pointers in place, including nested types (such as lists, + * dictionaries, and tuples). + * + * Arguments: + * ufunc - the original generic function + * new_ufunc - the new function being created with concrete types + * generic_type - the generic type to be replaced (may be a nested type) + * specific_type - pointer to the location where the concrete type should be + * set + * func_type - pointer to the function type to update (may be NULL) + */ + static void +update_generic_type( + ufunc_T *ufunc, + ufunc_T *new_ufunc, + type_T *generic_type, + type_T **specific_type, + type_T **func_type) +{ + int idx; + + switch (generic_type->tt_type) + { + case VAR_ANY: + idx = get_generic_type_index(ufunc, generic_type); + if (idx != -1) + { + *specific_type = new_ufunc->uf_generic_args[idx].gt_type; + if (func_type != NULL) + *func_type = new_ufunc->uf_generic_args[idx].gt_type; + } + break; + case VAR_LIST: + case VAR_DICT: + update_generic_type(ufunc, new_ufunc, generic_type->tt_member, + &(*specific_type)->tt_member, + func_type != NULL ? &(*func_type)->tt_member : NULL); + break; + case VAR_TUPLE: + for (int i = 0; i < generic_type->tt_argcount; i++) + update_generic_type(ufunc, new_ufunc, + generic_type->tt_args[i], + &(*specific_type)->tt_args[i], + func_type != NULL ? &(*func_type)->tt_args[i] : NULL); + break; + case VAR_FUNC: + for (int i = 0; i < generic_type->tt_argcount; i++) + update_generic_type(ufunc, new_ufunc, + generic_type->tt_args[i], + &(*specific_type)->tt_args[i], + func_type != NULL ? &(*func_type)->tt_args[i] : NULL); + update_generic_type(ufunc, new_ufunc, + generic_type->tt_member, + &(*specific_type)->tt_member, + func_type != NULL ? &(*func_type)->tt_member : NULL); + break; + default: + break; + } +} + +/* + * Adds a new concrete instance of a generic function for a specific set of + * type arguments. + * + * Arguments: + * fp - the original generic function to instantiate + * key - a string key representing the specific type arguments (used for + * lookup) + * gfatab - generic function args table containing the parsed type + * arguments and their names + * + * Returns: + * Pointer to the new ufunc_T representing the instantiated function, + * or NULL if the function already exists or on allocation failure. + * + * This function: + * - Checks if a function with the given type arguments already exists. + * - Allocates and initializes a new function instance with the specific + * types. + * - Updates the function's name and expanded name to include the type + * arguments. + * - Copies and updates all relevant type information (argument types, return + * type, vararg type, function type), replacing generic types with the + * actual types. + * - Sets the new function's status to UF_TO_BE_COMPILED. + * - Registers the new function in the generic function's lookup table. + */ + static ufunc_T * +generic_func_add(ufunc_T *fp, char_u *key, gfargs_tab_T *gfatab) +{ + hashtab_T *ht = &fp->uf_generic_functab; + long_u hash; + hashitem_T *hi; + int i; + + hash = hash_hash(key); + hi = hash_lookup(ht, key, hash); + if (!HASHITEM_EMPTY(hi)) + return NULL; + + size_t keylen = STRLEN(key); + gfitem_T *gfitem = alloc(sizeof(gfitem_T) + keylen); + if (gfitem == NULL) + return NULL; + + STRCPY(gfitem->gfi_name, key); + + ufunc_T *new_fp = copy_function(fp, (int)(keylen + 2)); + if (new_fp == NULL) + { + vim_free(gfitem); + return NULL; + } + + new_fp->uf_generic_arg_types = gfatab->gfat_arg_types; + // now that the type arguments is copied, remove the reference to the type + // arguments + ga_init(&gfatab->gfat_arg_types); + + if (fp->uf_class != NULL) + new_fp->uf_class = fp->uf_class; + + // Create a new name for the function: name + new_fp->uf_name[new_fp->uf_namelen] = '<'; + STRCPY(new_fp->uf_name + new_fp->uf_namelen + 1, key); + new_fp->uf_name[new_fp->uf_namelen + keylen + 1] = '>'; + new_fp->uf_namelen += keylen + 2; + + if (new_fp->uf_name_exp != NULL) + { + char_u *new_name_exp = alloc(STRLEN(new_fp->uf_name_exp) + keylen + 3); + if (new_name_exp != NULL) + { + STRCPY(new_name_exp, new_fp->uf_name_exp); + STRCAT(new_name_exp, "<"); + STRCAT(new_name_exp, key); + STRCAT(new_name_exp, ">"); + vim_free(new_fp->uf_name_exp); + new_fp->uf_name_exp = new_name_exp; + } + } + + gfitem->gfi_ufunc = new_fp; + gfitem->gfi_ufunc->uf_def_status = UF_TO_BE_COMPILED; + + // create a copy of + // - all the argument types + // - return type + // - vararg type + // - function type + // if any generic type is used, it will be replaced below). + for (i = 0; i < fp->uf_args.ga_len; i++) + new_fp->uf_arg_types[i] = copy_type_deep(fp->uf_arg_types[i], + &new_fp->uf_type_list); + + if (fp->uf_ret_type != NULL) + new_fp->uf_ret_type = copy_type_deep(fp->uf_ret_type, + &new_fp->uf_type_list); + + if (fp->uf_va_type != NULL) + new_fp->uf_va_type = copy_type_deep(fp->uf_va_type, + &new_fp->uf_type_list); + + if (fp->uf_func_type != NULL) + new_fp->uf_func_type = copy_type_deep(fp->uf_func_type, + &new_fp->uf_type_list); + + // Replace the t_any generic types with the actual types + for (i = 0; i < fp->uf_generic_argcount; i++) + { + generic_T *generic_arg; + generic_arg = (generic_T *)gfatab->gfat_args.ga_data + i; + generic_T *gt = &new_fp->uf_generic_args[i]; + gt->gt_type = generic_arg->gt_type; + } + + // Update any generic types in the function arguments + for (i = 0; i < fp->uf_args.ga_len; i++) + update_generic_type(fp, new_fp, fp->uf_arg_types[i], + &new_fp->uf_arg_types[i], + &new_fp->uf_func_type->tt_args[i]); + + // Update the vararg type if it uses generic types + if (fp->uf_va_type != NULL) + update_generic_type(fp, new_fp, fp->uf_va_type, &new_fp->uf_va_type, + NULL); + + // Update the return type if it is a generic type + if (fp->uf_ret_type != NULL) + update_generic_type(fp, new_fp, fp->uf_ret_type, &new_fp->uf_ret_type, + &new_fp->uf_func_type->tt_member); + + hash_add_item(ht, hi, gfitem->gfi_name, hash); + + return new_fp; +} + +/* + * Looks up a concrete instance of a generic function "fp" using the type + * arguments specified in "gfatab". + * + * The lookup key is constructed by concatenating the type argument names from + * "gfatab", separated by ", ", and stored in the provided growarray + * "gfkey_gap". The contents of "gfkey_gap" will be overwritten. + * + * Arguments: + * fp - the generic function to search in + * gfatab - generic function args table containing the parsed type + * arguments and their names + * gfkey_gap - growarray used to build and store the lookup key string + * + * Returns: + * Pointer to the ufunc_T representing the concrete function if found, or + * NULL if no matching function exists. + */ + static ufunc_T * +generic_lookup_func(ufunc_T *fp, gfargs_tab_T *gfatab, garray_T *gfkey_gap) +{ + hashtab_T *ht = &fp->uf_generic_functab; + hashitem_T *hi; + + for (int i = 0; i < gfatab->gfat_args.ga_len; i++) + { + generic_T *generic_arg; + + generic_arg = (generic_T *)gfatab->gfat_args.ga_data + i; + ga_concat(gfkey_gap, generic_arg->gt_name); + + if (i != gfatab->gfat_args.ga_len - 1) + { + ga_append(gfkey_gap, ','); + ga_append(gfkey_gap, ' '); + } + } + ga_append(gfkey_gap, NUL); + + char_u *key = ((char_u *)gfkey_gap->ga_data); + + hi = hash_find(ht, key); + + if (HASHITEM_EMPTY(hi)) + return NULL; + + gfitem_T *gfitem = HI2GFITEM(hi); + return gfitem->gfi_ufunc; +} + +/* + * Returns a concrete instance of the generic function "fp" using the type + * arguments specified in "gfatab". If such an instance does not exist, + * it is created and registered. + * + * Arguments: + * fp - the generic function to instantiate + * gfatab - generic function args table containing the parsed type + * arguments and their names + * + * Returns: + * Pointer to the ufunc_T representing the concrete function instance, + * or NULL if the type arguments are invalid or on allocation failure. + * + * Behavior: + * - If "fp" is not a generic function and no type arguments are given, + * returns "fp" as-is. + * - If "fp" is not generic but type arguments are given, reports an error + * and returns NULL. + * - Validates the number of type arguments, reporting errors for missing, + * too few, or too many. + * - Looks up an existing function instance with the given types. + * - If not found, creates and registers a new function instance. + */ + ufunc_T * +generic_func_get(ufunc_T *fp, gfargs_tab_T *gfatab) +{ + char *emsg = NULL; + + if (!IS_GENERIC_FUNC(fp)) + { + if (gfatab && generic_func_args_table_size(gfatab) > 0) + { + emsg_funcname(e_not_a_generic_function_str, fp->uf_name); + return NULL; + } + return fp; + } + + if (gfatab == NULL || gfatab->gfat_args.ga_len == 0) + emsg = e_generic_func_missing_type_args_str; + else if (gfatab->gfat_args.ga_len < fp->uf_generic_argcount) + emsg = e_not_enough_types_for_generic_function_str; + else if (gfatab->gfat_args.ga_len > fp->uf_generic_argcount) + emsg = e_too_many_types_for_generic_function_str; + + if (emsg != NULL) + { + emsg_funcname(emsg, printable_func_name(fp)); + return NULL; + } + + // generic function call + garray_T gfkey_ga; + + ga_init2(&gfkey_ga, 1, 80); + + // Look up the function with specific types + ufunc_T *generic_fp = generic_lookup_func(fp, gfatab, &gfkey_ga); + if (generic_fp == NULL) + // generic function with these type arguments doesn't exist. + // Create a new one. + generic_fp = generic_func_add(fp, (char_u *)gfkey_ga.ga_data, gfatab); + ga_clear(&gfkey_ga); + + return generic_fp; +} + +/* + * Looks up or creates a concrete instance of a generic function "ufunc" using + * the type arguments specified after the function name in "name". + * + * On entry, "name" points to the function name, and "*argp" points to the + * opening '<' of the type argument list (i.e., name + namelen). + * + * Arguments: + * ufunc - the generic function to instantiate or look up + * name - the function name, followed by the type argument list + * argp - pointer to a pointer to the type argument list (should point to + * '<'); on success, advanced to the character after the closing '>' + * + * Returns: + * Pointer to the ufunc_T representing the concrete function instance if + * successful, or NULL if parsing fails or the instance cannot be created. + * + * This function: + * - Parses the type arguments from the string after the function name. + * - Looks up an existing function instance with those type arguments. + * - If not found, creates and registers a new function instance. + * - Advances "*argp" to after the type argument list on success. + */ + ufunc_T * +find_generic_func(ufunc_T *ufunc, char_u *name, char_u **argp) +{ + gfargs_tab_T gfatab; + char_u *p; + ufunc_T *new_ufunc = NULL; + + generic_func_args_table_init(&gfatab); + + // Get the list of types following the name + p = parse_generic_func_type_args(name, *argp - name, *argp, &gfatab, NULL); + if (p != NULL) + { + new_ufunc = generic_func_get(ufunc, &gfatab); + *argp = p; + } + + generic_func_args_table_clear(&gfatab); + + return new_ufunc; +} + +/* + * Searches for a generic type with the given name "gt_name" in the generic + * function "ufunc". + * + * Arguments: + * gt_name - the name of the generic type to search for + * ufunc - the generic function in which to search for the type + * + * Returns: + * Pointer to the type_T representing the found generic type, + * or NULL if the type is not found or if "ufunc" is not a generic function. + */ + static type_T * +find_generic_type_in_ufunc(char_u *gt_name, size_t name_len, ufunc_T *ufunc) +{ + if (!IS_GENERIC_FUNC(ufunc)) + return NULL; + + for (int i = 0; i < ufunc->uf_generic_argcount; i++) + { + generic_T *generic; + + generic = ((generic_T *)ufunc->uf_generic_args) + i; + if (STRNCMP(generic->gt_name, gt_name, name_len) == 0) + { + type_T *type = generic->gt_type; + return type; + } + } + + return NULL; +} + +/* + * Searches for a generic type with the given name "gt_name" in the current + * function context "cctx" and its outer (enclosing) contexts, if necessary. + * + * Arguments: + * gt_name - the name of the generic type to search for + * cctx - the current compile context, which may be nested + * + * Returns: + * Pointer to the type_T representing the found generic type, + * or NULL if the type is not found in the current or any outer context. + */ + static type_T * +find_generic_type_in_cctx(char_u *gt_name, size_t name_len, cctx_T *cctx) +{ + type_T *type; + + type = find_generic_type_in_ufunc(gt_name, name_len, cctx->ctx_ufunc); + if (type != NULL) + return type; + + if (cctx->ctx_outer != NULL) + return find_generic_type_in_cctx(gt_name, name_len, cctx->ctx_outer); + + return NULL; +} + +/* + * Looks up a generic type with the given name "gt_name" in the generic + * function "ufunc". If not found, searches in the enclosing compile context + * "cctx" (for nested functions). + * + * Arguments: + * gt_name - the name of the generic type to search for + * ufunc - the generic function to search in first (may be NULL) + * cctx - the compile context to search in outer functions if not found + * in "ufunc" (may be NULL) + * + * Returns: + * Pointer to the type_T representing the found generic type, or NULL if the + * type is not found in the given function or any outer context. + */ + type_T * +find_generic_type( + char_u *gt_name, + size_t name_len, + ufunc_T *ufunc, + cctx_T *cctx) +{ + if (ufunc != NULL) + { + type_T *type = find_generic_type_in_ufunc(gt_name, name_len, ufunc); + if (type != NULL) + return type; + } + + if (cctx != NULL && ufunc != cctx->ctx_ufunc) + return find_generic_type_in_cctx(gt_name, name_len, cctx); + + return NULL; +} + +/* + * Frees all concrete function instances stored in the generic function table + * of "fp". This includes freeing each instantiated function and its + * associated gfitem_T structure, and clearing the hash table. + * + * Arguments: + * fp - the generic function whose function table should be freed + */ + static void +free_generic_functab(ufunc_T *fp) +{ + hashtab_T *ht = &fp->uf_generic_functab; + long todo; + hashitem_T *hi; + + todo = (long)ht->ht_used; + FOR_ALL_HASHTAB_ITEMS(ht, hi, todo) + { + if (!HASHITEM_EMPTY(hi)) + { + gfitem_T *gfitem = HI2GFITEM(hi); + + func_clear_free(gfitem->gfi_ufunc, FALSE); + vim_free(gfitem); + --todo; + } + } + hash_clear(ht); +} + +/* + * Frees all memory and state associated with a generic function "fp". + * This includes the generic type list, generic argument list, and all + * concrete function instances in the generic function table. + * + * Arguments: + * fp - the generic function to clear + */ + void +generic_func_clear_items(ufunc_T *fp) +{ + VIM_CLEAR(fp->uf_generic_param_types); + clear_type_list(&fp->uf_generic_arg_types); + for (int i = 0; i < fp->uf_generic_argcount; i++) + VIM_CLEAR(fp->uf_generic_args[i].gt_name); + VIM_CLEAR(fp->uf_generic_args); + free_generic_functab(fp); + fp->uf_flags &= ~FC_GENERIC; +} + +#endif // FEAT_EVAL diff --git a/src/vim9instr.c b/src/vim9instr.c index d4593ea138..9a501dcbb9 100644 --- a/src/vim9instr.c +++ b/src/vim9instr.c @@ -204,6 +204,7 @@ may_generate_2STRING(int offset, int tostring_flags, cctx_T *cctx) RETURN_OK_IF_SKIP(cctx); type = get_type_on_stack(cctx, -1 - offset); + switch (type->tt_type) { // nothing to be done @@ -2125,6 +2126,7 @@ check_func_args_from_type( return OK; } + /* * Generate an ISN_PCALL instruction. * "type" is the type of the FuncRef. diff --git a/src/vim9script.c b/src/vim9script.c index 7e33770a04..ece4a3acf1 100644 --- a/src/vim9script.c +++ b/src/vim9script.c @@ -846,7 +846,7 @@ vim9_declare_scriptvar(exarg_T *eap, char_u *arg) // parse type, check for reserved name p = skipwhite(p + 1); - type = parse_type(&p, &si->sn_type_list, TRUE); + type = parse_type(&p, &si->sn_type_list, NULL, NULL, TRUE); if (type == NULL || check_reserved_name(name, FALSE) == FAIL) { vim_free(name); diff --git a/src/vim9type.c b/src/vim9type.c index ab5e077528..62e9344ef9 100644 --- a/src/vim9type.c +++ b/src/vim9type.c @@ -101,7 +101,7 @@ copy_type_deep_rec(type_T *type, garray_T *type_gap, garray_T *seen_types) * Make a deep copy of "type". * When allocation fails returns "type". */ - static type_T * + type_T * copy_type_deep(type_T *type, garray_T *type_gap) { garray_T seen_types; @@ -342,7 +342,10 @@ get_list_type(type_T *member_type, garray_T *type_gap) type_T *type; // recognize commonly used types - if (member_type == NULL || member_type->tt_type == VAR_ANY) + // A generic type is t_any initially before being set to a concrete type + // later. So don't use the static t_list_any for a generic type. + if (member_type == NULL || (member_type->tt_type == VAR_ANY + && !IS_GENERIC_TYPE(member_type))) return &t_list_any; if (member_type->tt_type == VAR_VOID || member_type->tt_type == VAR_UNKNOWN) @@ -810,7 +813,29 @@ fp_typval2type(typval_T *tv, garray_T *type_gap) type_gap); } else - ufunc = find_func(name, FALSE); + { + // Check if name contains "<". If it does, then replace "<" with + // NUL and lookup the function and then look up the specific + // generic function using the supplied types. + char_u *p = generic_func_find_open_bracket(name); + if (p == NULL) + ufunc = find_func(name, FALSE); + else + { + // generic function + char_u cc = *p; + + *p = NUL; + ufunc = find_func(name, FALSE); + *p = cc; + if (ufunc != NULL && IS_GENERIC_FUNC(ufunc)) + { + ufunc = find_generic_func(ufunc, name, &p); + if (ufunc == NULL) + return NULL; + } + } + } } if (ufunc != NULL) { @@ -1582,7 +1607,9 @@ parse_type_member( type_T *type, garray_T *type_gap, int give_error, - char *info) + char *info, + ufunc_T *ufunc, + cctx_T *cctx) { char_u *arg_start = *arg; type_T *member_type; @@ -1601,7 +1628,7 @@ parse_type_member( } *arg = skipwhite(*arg + 1); - member_type = parse_type(arg, type_gap, give_error); + member_type = parse_type(arg, type_gap, ufunc, cctx, give_error); if (member_type == NULL) return NULL; @@ -1625,7 +1652,13 @@ parse_type_member( * Return NULL for failure. */ static type_T * -parse_type_func(char_u **arg, size_t len, garray_T *type_gap, int give_error) +parse_type_func( + char_u **arg, + size_t len, + garray_T *type_gap, + int give_error, + ufunc_T *ufunc, + cctx_T *cctx) { char_u *p; type_T *type; @@ -1665,7 +1698,7 @@ parse_type_func(char_u **arg, size_t len, garray_T *type_gap, int give_error) return NULL; } - type = parse_type(&p, type_gap, give_error); + type = parse_type(&p, type_gap, ufunc, cctx, give_error); if (type == NULL) return NULL; if ((flags & TTFLAG_VARARGS) != 0 && type->tt_type != VAR_LIST) @@ -1725,7 +1758,7 @@ parse_type_func(char_u **arg, size_t len, garray_T *type_gap, int give_error) if (!VIM_ISWHITE(**arg) && give_error) semsg(_(e_white_space_required_after_str_str), ":", *arg - 1); *arg = skipwhite(*arg); - ret_type = parse_type(arg, type_gap, give_error); + ret_type = parse_type(arg, type_gap, ufunc, cctx, give_error); if (ret_type == NULL) return NULL; } @@ -1755,7 +1788,12 @@ parse_type_func(char_u **arg, size_t len, garray_T *type_gap, int give_error) * Return NULL for failure. */ static type_T * -parse_type_tuple(char_u **arg, garray_T *type_gap, int give_error) +parse_type_tuple( + char_u **arg, + garray_T *type_gap, + int give_error, + ufunc_T *ufunc, + cctx_T *cctx) { char_u *p; type_T *type; @@ -1792,7 +1830,7 @@ parse_type_tuple(char_u **arg, garray_T *type_gap, int give_error) p += 3; } - type = parse_type(&p, type_gap, give_error); + type = parse_type(&p, type_gap, ufunc, cctx, give_error); if (type == NULL) goto on_err; @@ -1865,7 +1903,11 @@ on_err: * Return NULL for failure. */ static type_T * -parse_type_object(char_u **arg, garray_T *type_gap, int give_error) +parse_type_object( + char_u **arg, + garray_T *type_gap, + int give_error, + cctx_T *cctx) { char_u *arg_start = *arg; type_T *object_type; @@ -1889,7 +1931,7 @@ parse_type_object(char_u **arg, garray_T *type_gap, int give_error) // skip spaces following "object<" *arg = skipwhite(*arg + 1); - object_type = parse_type(arg, type_gap, give_error); + object_type = parse_type(arg, type_gap, NULL, cctx, give_error); if (object_type == NULL) return NULL; @@ -1926,7 +1968,9 @@ parse_type_user_defined( char_u **arg, size_t len, garray_T *type_gap, - int give_error) + int give_error, + ufunc_T *ufunc, + cctx_T *cctx) { int did_emsg_before = did_emsg; typval_T tv; @@ -1968,6 +2012,14 @@ parse_type_user_defined( clear_tv(&tv); } + // Check whether it is a generic type + type_T *type = find_generic_type(*arg, len, ufunc, cctx); + if (type != NULL) + { + *arg += len; + return type; + } + if (give_error && (did_emsg == did_emsg_before)) { char_u *p = skip_type(*arg, FALSE); @@ -1987,7 +2039,12 @@ parse_type_user_defined( * Return NULL for failure. */ type_T * -parse_type(char_u **arg, garray_T *type_gap, int give_error) +parse_type( + char_u **arg, + garray_T *type_gap, + ufunc_T *ufunc, + cctx_T *cctx, + int give_error) { char_u *p = *arg; size_t len; @@ -2029,8 +2086,9 @@ parse_type(char_u **arg, garray_T *type_gap, int give_error) if (len == 4 && STRNCMP(*arg, "dict", len) == 0) { *arg += len; - return parse_type_member(arg, &t_dict_any, - type_gap, give_error, "dict"); + return parse_type_member(arg, &t_dict_any, type_gap, + give_error, "dict", ufunc, + cctx); } break; case 'f': @@ -2040,7 +2098,8 @@ parse_type(char_u **arg, garray_T *type_gap, int give_error) return &t_float; } if (len == 4 && STRNCMP(*arg, "func", len) == 0) - return parse_type_func(arg, len, type_gap, give_error); + return parse_type_func(arg, len, type_gap, give_error, ufunc, + cctx); break; case 'j': if (len == 3 && STRNCMP(*arg, "job", len) == 0) @@ -2053,8 +2112,9 @@ parse_type(char_u **arg, garray_T *type_gap, int give_error) if (len == 4 && STRNCMP(*arg, "list", len) == 0) { *arg += len; - return parse_type_member(arg, &t_list_any, - type_gap, give_error, "list"); + return parse_type_member(arg, &t_list_any, type_gap, + give_error, "list", ufunc, + cctx); } break; case 'n': @@ -2068,7 +2128,7 @@ parse_type(char_u **arg, garray_T *type_gap, int give_error) if (len == 6 && STRNCMP(*arg, "object", len) == 0) { *arg += len; - return parse_type_object(arg, type_gap, give_error); + return parse_type_object(arg, type_gap, give_error, cctx); } break; case 's': @@ -2082,7 +2142,8 @@ parse_type(char_u **arg, garray_T *type_gap, int give_error) if (len == 5 && STRNCMP(*arg, "tuple", len) == 0) { *arg += len; - return parse_type_tuple(arg, type_gap, give_error); + return parse_type_tuple(arg, type_gap, give_error, ufunc, + cctx); } break; case 'v': @@ -2095,7 +2156,8 @@ parse_type(char_u **arg, garray_T *type_gap, int give_error) } // User defined type - return parse_type_user_defined(arg, len, type_gap, give_error); + return parse_type_user_defined(arg, len, type_gap, give_error, ufunc, + cctx); } /*