From 27a63798bc4c7e7bf2aa71c143aaed34c4c9844b Mon Sep 17 00:00:00 2001 From: fubark Date: Mon, 2 Sep 2024 11:23:24 -0400 Subject: [PATCH] Forbid function template overloading. Lazily resolve param types. Revise function and value template syntax. --- docs/docs.md | 62 ++- exts/sublime/cyber.sublime-syntax | 2 +- exts/vim/syntax/cyber.vim | 2 +- src/ast.zig | 16 +- src/bc_gen.zig | 4 +- src/builtins/builtins.zig | 4 +- src/builtins/builtins_vm.cy | 64 +-- src/chunk.zig | 2 - src/compiler.zig | 17 +- src/cte.zig | 113 ++-- src/module.zig | 25 +- src/parser.zig | 168 ++++-- src/sema.zig | 513 +++++++++--------- src/sema_func.zig | 529 ++++++++++++------- src/std/os_ffi.zig | 4 +- src/std/test.cy | 10 +- src/sym.zig | 160 ++++-- src/tokenizer.zig | 6 +- src/types.zig | 9 +- src/vm.h | 2 +- src/vm.zig | 6 +- test/functions/template_functions.cy | 8 +- test/functions/template_value.cy | 4 +- test/functions/template_value_host_error.cy | 5 +- test/functions/template_value_throw_error.cy | 2 +- test/memory/default_memory.cy | 4 +- test/types/template_dep_param_type_error.cy | 2 +- 27 files changed, 966 insertions(+), 777 deletions(-) diff --git a/docs/docs.md b/docs/docs.md index 2dc31446a..01b495e64 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -1561,7 +1561,7 @@ Note that invoking the template again with the same argument(s) returns the same ### Type specialization. [Value templates](#value-templates) can be used to specialize type templates: ```cy -func List[T type] type: +def List[T type] type: if T == dyn: return DynList else: @@ -2024,7 +2024,6 @@ The `try catch` statement, `try else` and `try` expressions provide a way to cat * [Explicit template call.](#explicit-template-call) * [Expand function.](#expand-function) * [Infer param type.](#infer-param-type) -* [Value templates.](#value-templates) @@ -2274,31 +2273,31 @@ There is no need for `#` if the caller is already in a compile-time context: *Co ## Function templates. Function declarations can include template parameters to create a function template: ```cy -func add(#T type, a T, b T) T: +func add[T](T type, a T, b T) T: return a + b ``` -Template parameters are prefixed with `#`. -Unlike type templates, function templates allow template parameters to be declared alongside runtime parameters. +Only the template parameter names are declared. +Their types are then inferred from the function signature. ### Explicit template call. -When the function is invoked with template argument(s), a special version of the function is generated and used: +When the function is invoked with template argument(s), a new runtime function is generated and used: ```cy print add(int, 1, 2) --> 3 print add(float, 1, 2) --> 3.0 ``` -Note that invoking the function again with the same argument(s) uses the same generated function. In other words, the generated function is always memoized from the template arguments. +Note that invoking the function again with the same template argument(s) uses the same generated function. In other words, the generated function is always memoized from the template arguments. ### Expand function. -Since functions may contain template and runtime parameters, the index operator is used to expand the function with just template arguments and return the generated function: +The function template can be explicitly expanded to a runtime function: ```cy var addInt = add[int] print addInt(1, 2) --> 3 ``` ### Infer param type. -When a template parameter is declared in the type specifier, it's inferred from the argument's type: +When a template parameter is first encountered in a function parameter's type specifier, it's inferred from the argument's type: ```cy -func add(a #T, b T) T: +func add[T](a T, b T) T: return a + b print add(1, 2) --> 3 @@ -2308,31 +2307,10 @@ In the above example, `add[int]` and `add[float]` were inferred from the functio Nested template parameters can also be inferred: ```cy -func set(m Map[#K, #V], key K, val V): +func set[K, V](m Map[K, V], key K, val V): m.set(key, val) ``` -## Value templates. -A value template returns a memoized value after being invoked with template arguments just once. It's declared with `func` but the template parameters are delimited with brackets. - -This is different from a function template which generates multiple runtime functions based on its template parameters. A value template does not generate a runtime function and instead expands to a value at compile-time: -```cy -func GetType[ID String] type: - if ID == 'bool': - return bool - else ID == 'int': - return int - else ID == 'String': - return String - else - throw error.Unsupported - -var a GetType['int'] = 123 -print a --> 123 -``` -This can be useful to evaluate compile-time logic to create new types or specialize other templates. -Any compile-time compatible type can also be returned. - # Modules.
@@ -3400,6 +3378,7 @@ print str.trim(.left, ' ') * [Reflection.](#reflection) * [Attributes.](#attributes) * [Templates.](#templates) + * [Value templates.](#value-templates) * [Macros.](#macros) * [Compile-time execution.](#compile-time-execution) * [Builtin types.](#builtin-types) @@ -3570,6 +3549,25 @@ Templates enables parametric polymorphism for types and functions. Template argu See [Type Declarations / Type templates](#type-templates) and [Functions / Function templates](#function-templates). +### Value templates. +A value template returns a memoized value after being invoked with template arguments at compile-time. It's declared with `def`: +```cy +def StrType[ID String] type: + if ID == 'bool': + return bool + else ID == 'int': + return int + else ID == 'String': + return String + else + throw error.Unsupported + +var a StrType['int'] = 123 +print a --> 123 +``` +This can be useful to evaluate compile-time logic to create new types or specialize other templates. +Any compile-time compatible type can also be returned. + ## Macros. > _Planned Feature_ diff --git a/exts/sublime/cyber.sublime-syntax b/exts/sublime/cyber.sublime-syntax index 41d4e7ea1..0f23828c3 100644 --- a/exts/sublime/cyber.sublime-syntax +++ b/exts/sublime/cyber.sublime-syntax @@ -37,7 +37,7 @@ contexts: - match: '\b(or|and|not)\b' scope: keyword.operator.cyber - - match: '\b(var|const|as|context|dyn)\b' + - match: '\b(var|def|as|context|dyn)\b' scope: keyword.variable.cyber - match: '\b(func|return|self)\b' diff --git a/exts/vim/syntax/cyber.vim b/exts/vim/syntax/cyber.vim index 96dfe7ae2..5d9ff72fb 100644 --- a/exts/vim/syntax/cyber.vim +++ b/exts/vim/syntax/cyber.vim @@ -2,7 +2,7 @@ syntax keyword cyberKeyword \ if else switch while for each break continue pass \ or and not is - \ var as dyn + \ var as dyn def \ func return \ coinit coyield coresume \ type object enum true false none diff --git a/src/ast.zig b/src/ast.zig index 6d0cf66ee..88457e6cb 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -409,7 +409,10 @@ pub const FuncDecl = struct { pub const FuncParam = struct { name_type: *Node align(8), type: ?*Node, - template: bool, + + // Used by sema to indicate that the parameter's type is inferred. + sema_infer_tparam: bool = false, + sema_tparam: bool = false, }; pub const UseAlias = struct { @@ -592,9 +595,10 @@ pub const StringTemplate = struct { parts: []*Node align(8), }; -const FuncSigType = enum(u8) { +pub const FuncSigType = enum(u8) { func, infer, + template, }; fn NodeData(comptime node_t: NodeType) type { @@ -778,10 +782,7 @@ pub const Node = struct { .forIterStmt => self.cast(.forIterStmt).pos, .forRangeStmt => self.cast(.forRangeStmt).pos, .funcDecl => self.cast(.funcDecl).pos, - .func_param => { - const param = self.cast(.func_param); - return param.name_type.pos() - @intFromBool(param.template); - }, + .func_param => self.cast(.func_param).name_type.pos(), .func_type => self.cast(.func_type).pos, .group => self.cast(.group).pos, .hexLit => self.cast(.hexLit).pos, @@ -1139,6 +1140,9 @@ pub const AstView = struct { if (decl.params.len == 0) { return false; } + if (decl.params[0].name_type.type() != .ident) { + return false; + } const param_name = self.nodeString(decl.params[0].name_type); return std.mem.eql(u8, param_name, "self"); } diff --git a/src/bc_gen.zig b/src/bc_gen.zig index dd05b4551..65a402225 100644 --- a/src/bc_gen.zig +++ b/src/bc_gen.zig @@ -37,7 +37,7 @@ pub fn genAll(c: *cy.Compiler) !void { log.tracev("prep type: {s}", .{stype.sym.name()}); const sym = stype.sym; - if (stype.info.ct_infer or stype.info.ct_ref) { + if (stype.info.ct_ref) { continue; } @@ -210,6 +210,7 @@ fn prepareSym(c: *cy.Compiler, sym: *cy.Sym) !void { }, .context_var, .template, + .func_template, .hostobj_t, .type, .chunk, @@ -233,7 +234,6 @@ fn prepareSym(c: *cy.Compiler, sym: *cy.Sym) !void { pub fn prepareFunc(c: *cy.Compiler, opt_group: ?rt.FuncGroupId, func: *cy.Func) !void { switch (func.type) { - .template, .trait => return, .userLambda => { if (cy.Trace) { diff --git a/src/builtins/builtins.zig b/src/builtins/builtins.zig index 47df2698b..9e2212af7 100644 --- a/src/builtins/builtins.zig +++ b/src/builtins/builtins.zig @@ -31,7 +31,7 @@ const func = cy.hostFuncEntry; const funcs = [_]C.HostFuncEntry{ // Utils. func("bitcast_", zErrFunc(bitcast)), - func("copy", zErrFunc(copy)), + func("copy_", zErrFunc(copy)), func("dump", zErrFunc(dump)), func("eprint", eprint), func("errorReport", zErrFunc(errorReport)), @@ -129,7 +129,7 @@ const funcs = [_]C.HostFuncEntry{ func("List.remove", bindings.listRemove), func("List.resize_", zErrFunc(bindings.listResize)), // .{"sort", bindings.listSort, .standard}, - func("List.fill", listFill), + func("List.fill_", listFill), // ListIterator func("ListIterator.next_", zErrFunc(bindings.listIteratorNext)), diff --git a/src/builtins/builtins_vm.cy b/src/builtins/builtins_vm.cy index c9ee06676..ed6e55b19 100644 --- a/src/builtins/builtins_vm.cy +++ b/src/builtins/builtins_vm.cy @@ -2,16 +2,16 @@ --| Functions. --| -func bitcast(#D type, val #S) D: +func bitcast[D, S](D type, val S) D: return bitcast_(D, typeid[D], val, typeid[S]) -@host -func bitcast_(#D type, dst_t int, val #S, src_t int) D +@host -func bitcast_[D, S](D type, dst_t int, val S, src_t int) D --| Copies a primitive value or creates a shallow copy of an object value. -func copy(val #T) T: - return copy(typeid[T], val) +func copy[T](val T) T: + return copy_(typeid[T], val) -@host -func copy(type_id int, val #T) T +@host -func copy_[T](type_id int, val T) T --| Dumps a detailed description of a value. @host func dump(val any) void @@ -47,10 +47,10 @@ func copy(val #T) T: --| Returns the statistics of the run in a map value. @host func performGC() Map -func ptrcast(#D type, val #S) *D: +func ptrcast[D, S](D type, val S) *D: return ptrcast_(D, typeid[S], val) -@host -func ptrcast_(#D type, src_t int, val #S) *D +@host -func ptrcast_[D, S](D type, src_t int, val S) *D --| Prints a value. The host determines how it is printed. @host func print(str any) void @@ -58,12 +58,12 @@ func ptrcast(#D type, val #S) *D: --| Queues a callback function as an async task. @host func queueTask(fn Func() void) void -@host func refcast(#T type, ptr *T) &T +@host func refcast[T](T type, ptr *T) &T --| Converts a rune to a string. @host func runestr(val int) String -func sizeof(#T type) int: +func sizeof[T](T type) int: return sizeof_(typeid[T]) @host func sizeof_(type_id int) int @@ -77,7 +77,7 @@ func typeof(t ExprType) type: --| --| Returns the type ID of a type. -func typeid[T type] int: +def typeid[T type] int: return (T).id() --| @@ -287,10 +287,10 @@ type float #float64_t: --| Creates a list with initial capacity of `n` and values set to `val`. --| If the value is an object, it is shallow copied `n` times. -func List.fill(val #T, n int) List[T]: - return List.fill(typeid[List[T]], typeid[T], val, n) +func List.fill[T](val T, n int) List[T]: + return List.fill_(typeid[List[T]], typeid[T], val, n) -@host -func List.fill(list_t int, val_t int, val #T, n int) List[T] +@host -func List.fill_[T](list_t int, val_t int, val T, n int) List[T] @host type ListIterator[T type] _: func next(self) ?T: @@ -534,7 +534,7 @@ type pointer[T type] #int64_t: @host func set(self, offset int, ctype symbol, val any) void --| Converts an `int` to a `pointer` value. -@host func pointer.$call(#T type, addr int) *T +@host func pointer.$call[T](T type, addr int) *T @host type ExternFunc _: @@ -562,24 +562,24 @@ type Option[T type] enum: @host type Future[T type] _ --| Returns a `Future[T]` that has a completed value. -func Future.complete(val #T) Future[T]: +func Future.complete[T](val T) Future[T]: return Future.complete_(val, typeid[Future[T]]) -@host -func Future.complete_(val #T, ret_type int) Future[T] +@host -func Future.complete_[T](val T, ret_type int) Future[T] -func Future.new(#T type) Future[T]: +func Future.new[T](T type) Future[T]: return Future.new_(T, typeid[Future[T]]) -@host -func Future.new_(#T type, ret_type int) Future[T] +@host -func Future.new_[T](T type, ret_type int) Future[T] @host type FutureResolver[T type] _: @host func complete(self, val T) void @host func future(self) Future[T] -func FutureResolver.new(#T type) FutureResolver[T]: +func FutureResolver.new[T](T type) FutureResolver[T]: return FutureResolver.new_(T, typeid[Future[T]], typeid[FutureResolver[T]]) -@host -func FutureResolver.new_(#T type, future_t int, ret_t int) FutureResolver[T] +@host -func FutureResolver.new_[T](T type, future_t int, ret_t int) FutureResolver[T] type Ref[T type] #int64_t @@ -710,20 +710,22 @@ type IMemory trait: type Memory: iface IMemory - func new(self, #T type) *T: - var bytes = self.iface.alloc(sizeof(T)) - return ptrcast(T, bytes.ptr) +func Memory.new[T](self, T type) *T: + var bytes = self.iface.alloc(sizeof(T)) + var ptr = ptrcast(T, bytes.ptr) + return ptr - func alloc(self, #T type, n int) [*]T: - var bytes = self.iface.alloc(sizeof(T) * n) - return ptrcast(T, bytes.ptr)[0..n] +func Memory.alloc[T](self, T type, n int) [*]T: + var bytes = self.iface.alloc(sizeof(T) * n) + var slice = ptrcast(T, bytes.ptr)[0..n] + return slice - func free(self, ptr *#T): - self.iface.free(ptrcast(byte, ptr)[0..sizeof(T)]) +func Memory.free[T](self, ptr *T): + self.iface.free(ptrcast(byte, ptr)[0..sizeof(T)]) - func free(self, slice [*]#T): - var bytes = ptrcast(byte, slice.ptr)[0..sizeof(T)*slice.len()] - self.iface.free(bytes) +func Memory.free_[T](self, slice [*]T): + var bytes = ptrcast(byte, slice.ptr)[0..sizeof(T)*slice.len()] + self.iface.free(bytes) type DefaultMemory: with IMemory diff --git a/src/chunk.zig b/src/chunk.zig index 2d6b7da02..42e1f0fa5 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -322,7 +322,6 @@ pub const Chunk = struct { self.use_alls.deinit(self.alloc); for (self.funcs.items) |func| { - func.deinit(self.alloc); self.alloc.destroy(func); } self.funcs.deinit(self.alloc); @@ -334,7 +333,6 @@ pub const Chunk = struct { self.syms.deinit(self.alloc); for (self.deferred_funcs.items) |func| { - func.deinit(self.alloc); self.alloc.destroy(func); } self.deferred_funcs.deinit(self.alloc); diff --git a/src/compiler.zig b/src/compiler.zig index c50747e06..7b8509969 100644 --- a/src/compiler.zig +++ b/src/compiler.zig @@ -138,9 +138,6 @@ pub const Compiler = struct { for (chunk.syms.items) |sym| { sym.deinitValues(self.vm); } - for (chunk.funcs.items) |func| { - func.deinitValues(self.vm); - } } } @@ -804,7 +801,11 @@ fn reserveSyms(self: *Compiler, core_sym: *cy.sym.Chunk) !void{ }, .template => { const decl = node.cast(.template); - _ = try sema.reserveTemplate(chunk, decl); + if (decl.child_decl.type() == .funcDecl and decl.child_decl.cast(.funcDecl).sig_t == .template) { + _ = try sema.reserveFuncTemplate(chunk, decl); + } else { + _ = try sema.reserveTemplate(chunk, decl); + } }, .staticDecl => { const sym = try sema.reserveVar(chunk, node.cast(.staticDecl)); @@ -964,11 +965,6 @@ pub var UpValueType = cy.sym.DummyType{ .type = bt.UpValue, }; -pub var CTInferType = cy.sym.DummyType{ - .head = cy.Sym.init(.dummy_t, null, "compt-infer"), - .type = bt.CTInfer, -}; - fn createDynMethodIds(self: *Compiler) !void { self.indexMID = try self.vm.ensureMethod("$index"); self.setIndexMID = try self.vm.ensureMethod("$setIndex"); @@ -1065,6 +1061,9 @@ fn resolveSyms(self: *Compiler) !void { .template => { try sema.ensureResolvedTemplate(chunk, @ptrCast(sym)); }, + .func_template => { + try sema.ensureResolvedFuncTemplate(chunk, @ptrCast(sym)); + }, .trait_t => {}, else => {}, } diff --git a/src/cte.zig b/src/cte.zig index 9bf8cedff..5cf3df758 100644 --- a/src/cte.zig +++ b/src/cte.zig @@ -37,7 +37,7 @@ pub fn pushNodeValuesCstr(c: *cy.Chunk, args: []const *ast.Node, template: *cy.s defer c.alloc.free(cstrName); const typeName = try c.sema.allocTypeName(res.type); defer c.alloc.free(typeName); - return c.reportErrorFmt("Expected type `{}`, got `{}`.", &.{v(cstrName), v(typeName)}, arg); + return c.reportErrorFmt("Expected type `{}`. Found `{}`.", &.{v(cstrName), v(typeName)}, arg); } } } @@ -46,7 +46,7 @@ pub fn pushNodeValuesCstr(c: *cy.Chunk, args: []const *ast.Node, template: *cy.s fn resolveTemplateParamType(c: *cy.Chunk, type_id: cy.TypeId, ct_args: [*]const cy.Value) !cy.TypeId { const type_e = c.sema.types.items[type_id]; if (type_e.kind == .ct_ref) { - const ct_arg = ct_args[type_e.data.ct_infer.ct_param_idx]; + const ct_arg = ct_args[type_e.data.ct_ref.ct_param_idx]; if (ct_arg.getTypeId() != bt.Type) { return error.TODO; } @@ -118,7 +118,7 @@ pub fn pushNodeValues(c: *cy.Chunk, args: []const *ast.Node) !void { } } -pub fn expandTemplateOnCallArgs(c: *cy.Chunk, template: *cy.sym.Template, args: []const *ast.Node, node: *ast.Node) !*cy.Sym { +pub fn expandTemplateForArgs(c: *cy.Chunk, template: *cy.sym.Template, args: []const *ast.Node, node: *ast.Node) !*cy.Sym { try sema.ensureResolvedTemplate(c, template); // Accumulate compile-time args. @@ -137,7 +137,7 @@ pub fn expandTemplateOnCallArgs(c: *cy.Chunk, template: *cy.sym.Template, args: return expandTemplate(c, template, arg_vals); } -pub fn expandCtFuncTemplateOnCallArgs(c: *cy.Chunk, template: *cy.sym.Template, args: []const *ast.Node, node: *ast.Node) !CtValue { +pub fn expandValueTemplateForArgs(c: *cy.Chunk, template: *cy.sym.Template, args: []const *ast.Node, node: *ast.Node) !CtValue { // Accumulate compile-time args. const valueStart = c.valueStack.items.len; defer { @@ -154,43 +154,42 @@ pub fn expandCtFuncTemplateOnCallArgs(c: *cy.Chunk, template: *cy.sym.Template, return expandValueTemplate(c, template, arg_vals); } -pub fn expandFuncTemplateOnCallArgs(c: *cy.Chunk, template: *cy.Func, args: []const *ast.Node, node: *ast.Node) !*cy.Func { +pub fn expandFuncTemplateForArgs(c: *cy.Chunk, template: *cy.sym.FuncTemplate, args: []const *ast.Node, node: *ast.Node) !*cy.Func { + try sema.ensureResolvedFuncTemplate(c, template); + + if (template.params.len != args.len) { + return c.reportErrorFmt("Expected {} arguments. Found {}.", &.{v(template.params.len), v(args.len)}, node); + } + // Accumulate compile-time args. - const typeStart = c.typeStack.items.len; - const valueStart = c.valueStack.items.len; + const value_start = c.valueStack.items.len; defer { - c.typeStack.items.len = typeStart; - // Values need to be released. - const values = c.valueStack.items[valueStart..]; + const values = c.valueStack.items[value_start..]; for (values) |val| { c.vm.release(val); } - c.valueStack.items.len = valueStart; + c.valueStack.items.len = value_start; } - try pushNodeValues(c, args); - - const argTypes = c.typeStack.items[typeStart..]; - const arg_vals = c.valueStack.items[valueStart..]; + for (args, 0..) |arg, i| { + const res = try resolveCtValue(c, arg); - // Check against template signature. - const func_template = template.data.template; - if (!cy.types.isTypeFuncSigCompat(c.compiler, @ptrCast(argTypes), .not_void, func_template.sig)) { - const sig = c.sema.getFuncSig(func_template.sig); - const params_s = try c.sema.allocFuncParamsStr(sig.params(), c); - defer c.alloc.free(params_s); - return c.reportErrorFmt( - \\Expected template expansion signature `{}[{}]`. - , &.{v(template.name()), v(params_s)}, node); + var param_t: cy.TypeId = undefined; + if (template.params[i].infer) { + param_t = res.value.getTypeId(); + } else { + const decl_idx = template.params[i].decl_idx; + param_t = try sema.resolveTypeSpecNode(c, template.func_params[decl_idx].type); + } + try c.valueStack.append(c.alloc, res.value); } + const arg_vals = c.valueStack.items[value_start..]; return expandFuncTemplate(c, template, arg_vals); } -pub fn expandFuncTemplate(c: *cy.Chunk, tfunc: *cy.sym.Func, args: []const cy.Value) !*cy.Func { - const template = tfunc.data.template; - +pub fn expandFuncTemplate(c: *cy.Chunk, template: *cy.sym.FuncTemplate, args: []const cy.Value) !*cy.Func { // Ensure variant func. const res = try template.variant_cache.getOrPutContext(c.alloc, args, .{ .sema = c.sema }); if (!res.found_existing) { @@ -206,13 +205,13 @@ pub fn expandFuncTemplate(c: *cy.Chunk, tfunc: *cy.sym.Func, args: []const cy.Va .type = .func, .args = args_dupe, .data = .{ .func = .{ - .template = tfunc.data.template, + .template = template, .func = undefined, }}, }; - const tchunk = tfunc.chunk(); - const new_func = try sema.reserveFuncTemplateVariant(tchunk, tfunc, tfunc.decl, variant); + const tchunk = template.chunk(); + const new_func = try sema.reserveFuncTemplateVariant(tchunk, template, template.decl.child_decl, variant); variant.data.func.func = new_func; res.key_ptr.* = args_dupe; res.value_ptr.* = variant; @@ -396,17 +395,14 @@ pub fn expandTemplate(c: *cy.Chunk, template: *cy.sym.Template, args: []const cy c.vm.retain(param); } - var ct_infer = false; var ct_dep = false; for (args) |arg| { if (arg.getTypeId() == bt.Type) { const type_id = arg.asHeapObject().type.type; const type_e = c.sema.types.items[type_id]; - ct_infer = ct_infer or type_e.info.ct_infer; ct_dep = ct_dep or type_e.info.ct_ref; } else if (arg.getTypeId() == bt.FuncSig) { const sig = c.sema.getFuncSig(@intCast(arg.asHeapObject().integer.val)); - ct_infer = ct_infer or sig.info.ct_infer; ct_dep = ct_dep or sig.info.ct_dep; } } @@ -429,7 +425,6 @@ pub fn expandTemplate(c: *cy.Chunk, template: *cy.sym.Template, args: []const cy variant.data.sym.sym = new_sym; const new_type = new_sym.getStaticType().?; - c.sema.types.items[new_type].info.ct_infer = ct_infer; c.sema.types.items[new_type].info.ct_ref = ct_dep; // Allow circular reference by resolving after the new symbol has been added to the cache. @@ -665,37 +660,37 @@ pub fn resolveCtExprOpt(c: *cy.Chunk, expr: *ast.Node) anyerror!?CtExpr { return c.reportErrorFmt("Unsupported array expression.", &.{}, expr); } const template = left.cast(.template); - if (template.kind == .ct_func) { - const val = try cte.expandCtFuncTemplateOnCallArgs(c, template, array_expr.args, expr); + if (template.kind == .value) { + const val = try cte.expandValueTemplateForArgs(c, template, array_expr.args, expr); return CtExpr.initValue(val); } else { - const sym = try cte.expandTemplateOnCallArgs(c, template, array_expr.args, expr); + const sym = try cte.expandTemplateForArgs(c, template, array_expr.args, expr); return CtExpr.initSym(sym); } }, .ptr_slice => { const ptr_slice = expr.cast(.ptr_slice); - const sym = try cte.expandTemplateOnCallArgs(c, c.sema.ptr_slice_tmpl, &.{ ptr_slice.elem }, expr); + const sym = try cte.expandTemplateForArgs(c, c.sema.ptr_slice_tmpl, &.{ ptr_slice.elem }, expr); return CtExpr.initSym(sym); }, .ptr => { const ptr = expr.cast(.ptr); - const sym = try cte.expandTemplateOnCallArgs(c, c.sema.pointer_tmpl, &.{ ptr.elem }, expr); + const sym = try cte.expandTemplateForArgs(c, c.sema.pointer_tmpl, &.{ ptr.elem }, expr); return CtExpr.initSym(sym); }, .ref => { const ref = expr.cast(.ref); - const sym = try cte.expandTemplateOnCallArgs(c, c.sema.ref_tmpl, &.{ ref.elem }, expr); + const sym = try cte.expandTemplateForArgs(c, c.sema.ref_tmpl, &.{ ref.elem }, expr); return CtExpr.initSym(sym); }, .ref_slice => { const ref_slice = expr.cast(.ref_slice); - const sym = try cte.expandTemplateOnCallArgs(c, c.sema.ref_slice_tmpl, &.{ ref_slice.elem }, expr); + const sym = try cte.expandTemplateForArgs(c, c.sema.ref_slice_tmpl, &.{ ref_slice.elem }, expr); return CtExpr.initSym(sym); }, .expandOpt => { const expand_opt = expr.cast(.expandOpt); - const sym =try cte.expandTemplateOnCallArgs(c, c.sema.option_tmpl, &.{expand_opt.param}, expr); + const sym =try cte.expandTemplateForArgs(c, c.sema.option_tmpl, &.{expand_opt.param}, expr); return CtExpr.initSym(sym); }, .accessExpr => { @@ -744,41 +739,7 @@ pub fn resolveCtExprOpt(c: *cy.Chunk, expr: *ast.Node) anyerror!?CtExpr { return CtExpr.initSym(sym); }, .comptimeExpr => { - const ctx = sema.getResolveContext(c); const ct_expr = expr.cast(.comptimeExpr); - if (ctx.parse_ct_inferred_params) { - if (ct_expr.child.type() != .ident) { - return c.reportErrorFmt("Expected identifier.", &.{}, ct_expr.child); - } - const param_name = c.ast.nodeString(ct_expr.child); - - const param_idx = ctx.ct_params.size; - const ref_t = try c.sema.ensureCtRefType(param_idx); - const ref_v = try c.vm.allocType(ref_t); - try sema.setResolveCtParam(c, param_name, ref_v); - - const infer_t = try c.sema.ensureCtInferType(param_idx); - const sym = c.sema.getTypeSym(infer_t); - return CtExpr.initSym(sym); - } - - if (ctx.expand_ct_inferred_params) { - const param_name = c.ast.nodeString(ct_expr.child); - const val = ctx.ct_params.get(param_name) orelse { - return c.reportErrorFmt("Could not find the compile-time parameter `{}`.", &.{v(param_name)}, ct_expr.child); - }; - if (val.getTypeId() != bt.Type) { - c.vm.retain(val); - return CtExpr.initValue(.{ - .type = val.getTypeId(), - .value = val, - }); - } else { - const type_id = val.asHeapObject().type.type; - const sym = c.sema.getTypeSym(type_id); - return CtExpr.initSym(sym); - } - } // Check for ct builtins. const mod = c.compiler.ct_builtins_chunk.?.sym.getMod(); diff --git a/src/module.zig b/src/module.zig index 68b3be381..872fdfebc 100644 --- a/src/module.zig +++ b/src/module.zig @@ -207,6 +207,13 @@ pub const ChunkExt = struct { return sym; } + pub fn reserveFuncTemplate(c: *cy.Chunk, parent: *cy.Sym, name: []const u8, tparams: []cy.sym.FuncTemplateParam, decl: *ast.TemplateDecl) !*cy.sym.FuncTemplate { + const sym = try c.createFuncTemplate(parent, name, tparams, decl); + const mod = parent.getMod().?; + try addUniqueSym(c, mod, name, @ptrCast(sym), @ptrCast(decl)); + return sym; + } + pub fn reserveTemplate(c: *cy.Chunk, parent: *cy.Sym, name: []const u8, kind: cy.sym.TemplateType, decl: *ast.TemplateDecl) !*cy.sym.Template { @@ -355,22 +362,6 @@ pub const ChunkExt = struct { func.numParams = @intCast(func_sig.params_len); } - pub fn reserveTemplateFunc( - c: *cy.Chunk, parent: *cy.Sym, name: []const u8, node: ?*ast.FuncDecl, is_method: bool, - ) !*cy.Func { - const mod = parent.getMod().?; - const sym = try prepareFuncSym(c, parent, mod, name, node); - const func = try c.createFunc(.template, sym, @ptrCast(node), is_method); - try c.funcs.append(c.alloc, func); - sym.addFunc(func); - return func; - } - - pub fn resolveTemplateFunc(c: *cy.Chunk, func: *cy.Func, func_sig: sema.FuncSigId, template: *cy.sym.FuncTemplate) !void { - func.data = .{ .template = template }; - try resolveFunc(c, func, func_sig); - } - pub fn reserveHostFunc( c: *cy.Chunk, parent: *cy.Sym, name: []const u8, node: ?*ast.FuncDecl, is_method: bool, deferred: bool, ) !*cy.Func { @@ -531,6 +522,7 @@ pub const ChunkExt = struct { .enum_t, .chunk, .distinct_t, + .func_template, .template, .placeholder, .dummy_t, @@ -609,6 +601,7 @@ pub const ChunkExt = struct { .enum_t, .chunk, .distinct_t, + .func_template, .template, .placeholder, .use_alias, diff --git a/src/parser.zig b/src/parser.zig index ec5f751ce..9a1248292 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -500,61 +500,77 @@ pub const Parser = struct { return self.reportError("Expected open parenthesis.", &.{}); } self.advance(); - return self.parseFuncParams(&.{}, false); + return self.parseFuncParams(&.{}); } fn genDynFuncParam(self: *Parser, ident: *ast.Node) !*ast.FuncParam { return self.ast.newNode(.func_param, .{ .name_type = ident, .type = null, - .template = false, }); } fn parseFuncParam(self: *Parser) anyerror!*ast.FuncParam { - var template_param = false; var name_type: *ast.Node = undefined; - if (self.peek().tag() == .pound) { - self.advance(); - if (self.peek().tag() != .ident) { - return self.reportError("Expected param name.", &.{}); - } - name_type = @ptrCast(try self.newSpanNode(.ident, self.next_pos)); - self.advance(); - template_param = true; - } else { - const next = self.peek(); - if (next.tag() == .ident) { - // Check for space between ident and next token otherwise it could be a type. - const next2 = self.peekAhead(1); - if (next2.pos() >= next.data.end_pos + 1) { - name_type = @ptrCast(try self.newSpanNode(.ident, self.next_pos)); - self.advance(); - } else { - name_type = (try self.parseTermExpr(.{})) orelse { - return self.reportError("Expected param.", &.{}); - }; - } + const next = self.peek(); + if (next.tag() == .ident) { + // Check for space between ident and next token otherwise it could be a type. + const next2 = self.peekAhead(1); + if (next2.pos() >= next.data.end_pos + 1) { + name_type = @ptrCast(try self.newSpanNode(.ident, self.next_pos)); + self.advance(); } else { name_type = (try self.parseTermExpr(.{})) orelse { return self.reportError("Expected param.", &.{}); }; } + } else { + name_type = (try self.parseTermExpr(.{})) orelse { + return self.reportError("Expected param.", &.{}); + }; } const type_spec = try self.parseOptTypeSpec(false); return self.ast.newNode(.func_param, .{ .name_type = name_type, .type = type_spec, - .template = template_param, }); } + fn parseTemplateParams(self: *Parser) ![]*ast.FuncParam { + const param_start = self.node_stack.items.len; + defer self.node_stack.items.len = param_start; + + if (self.peek().tag() == .right_bracket) { + self.advance(); + return &.{}; + } + + // Parse params. + var param = try self.parseFuncParam(); + try self.pushNode(@ptrCast(param)); + + while (true) { + switch (self.peek().tag()) { + .comma => { + self.advance(); + }, + .right_bracket => { + self.advance(); + break; + }, + else => return self.reportError("Expected `,`.", &.{}), + } + param = try self.parseFuncParam(); + try self.pushNode(@ptrCast(param)); + } + const params = self.node_stack.items[param_start..]; + return @ptrCast(try self.ast.dupeNodes(params)); + } + /// Assumes token at first param ident or right paren. /// Let sema check whether param types are required since it depends on the context. - fn parseFuncParams(self: *Parser, pre_params: []const *ast.FuncParam, comptime template: bool) ![]*ast.FuncParam { - const CloseDelim: cy.TokenType = if (template) .right_bracket else .right_paren; - + fn parseFuncParams(self: *Parser, pre_params: []const *ast.FuncParam) ![]*ast.FuncParam { const param_start = self.node_stack.items.len; defer self.node_stack.items.len = param_start; @@ -562,7 +578,7 @@ pub const Parser = struct { try self.node_stack.appendSlice(self.alloc, @ptrCast(pre_params)); } - if (self.peek().tag() == CloseDelim) { + if (self.peek().tag() == .right_paren) { self.advance(); return &.{}; } @@ -576,7 +592,7 @@ pub const Parser = struct { .comma => { self.advance(); }, - CloseDelim => { + .right_paren => { self.advance(); break; }, @@ -707,7 +723,7 @@ pub const Parser = struct { return error.TODO; } else { self.advance(); - const params = try self.parseFuncParams(&.{}, true); + const params = try self.parseTemplateParams(); return self.ast.newNodeErase(.template, .{ .params = params, .child_decl = undefined, @@ -1179,6 +1195,62 @@ pub const Parser = struct { allow_decl: bool, }; + fn parseDefDecl(self: *Parser, config: FuncDeclConfig) !*ast.Node { + if (!config.allow_decl) { + return self.reportError("Static declarations are not allowed here.", &.{}); + } + const start = self.next_pos; + // Assumes first token is the `const` keyword. + self.advance(); + + // Parse const name. + const name = (try self.parseOptNamePath()) orelse { + return self.reportError("Expected name identifier.", &.{}); + }; + + var template: *ast.TemplateDecl = undefined; + var params: []*ast.FuncParam = undefined; + if (self.peek().tag() != .left_bracket) { + return self.reportError("Expected left bracket.", &.{}); + } + + self.advance(); + params = try self.parseTemplateParams(); + template = try self.ast.newNode(.template, .{ + .params = params, + .child_decl = undefined, + }); + try self.staticDecls.append(self.alloc, @ptrCast(template)); + self.consumeWhitespaceTokens(); + + const ret = try self.parseFuncReturn(); + + var token = self.peek(); + if (token.tag() != .colon) { + return self.reportError("Expected colon.", &.{}); + } + + self.advance(); + + try self.pushBlock(); + const stmts = try self.parseSingleOrIndentedBodyStmts(); + _ = self.popBlock(); + + const decl = try self.ast.newNode(.funcDecl, .{ + .name = name, + .attrs = config.attrs, + .params = params, + .ret = ret, + .stmts = stmts, + .sig_t = .func, + .hidden = config.hidden, + .pos = self.tokenSrcPos(start), + }); + + template.child_decl = @ptrCast(decl); + return @ptrCast(template); + } + fn parseFuncDecl(self: *Parser, config: FuncDeclConfig) !*ast.Node { if (!config.allow_decl) { return self.reportError("`func` declarations are not allowed here.", &.{}); @@ -1192,19 +1264,27 @@ pub const Parser = struct { return self.reportError("Expected function name identifier.", &.{}); }; + var sig_t = ast.FuncSigType.func; var opt_template: ?*ast.TemplateDecl = null; var params: []*ast.FuncParam = undefined; if (self.peek().tag() == .left_bracket) { self.advance(); - params = try self.parseFuncParams(&.{}, true); + params = try self.parseTemplateParams(); opt_template = try self.ast.newNode(.template, .{ .params = params, .child_decl = undefined, }); try self.staticDecls.append(self.alloc, @ptrCast(opt_template)); self.consumeWhitespaceTokens(); - } else { + } + + if (self.peek().tag() == .left_paren) { params = try self.parseParenAndFuncParams(); + if (opt_template != null) { + sig_t = .template; + } + } else { + return self.reportError("Expected function parameter list.", &.{}); } const ret = try self.parseFuncReturn(); @@ -1227,7 +1307,7 @@ pub const Parser = struct { .params = params, .ret = ret, .stmts = stmts, - .sig_t = .func, + .sig_t = sig_t, .hidden = config.hidden, .pos = self.tokenSrcPos(start), }); @@ -1239,7 +1319,7 @@ pub const Parser = struct { .name = name, .params = params, .stmts = &.{}, - .sig_t = .func, + .sig_t = sig_t, .hidden = config.hidden, .pos = self.tokenSrcPos(start), }); @@ -1247,12 +1327,13 @@ pub const Parser = struct { if (opt_template) |template| { template.child_decl = @ptrCast(decl); + return @ptrCast(template); } else { if (self.cur_indent == 0) { try self.staticDecls.append(self.alloc, @ptrCast(decl)); } + return @ptrCast(decl); } - return @ptrCast(decl); } fn parseElseStmts(self: *Parser) ![]*ast.ElseBlock { @@ -1834,11 +1915,18 @@ pub const Parser = struct { .at => { return self.parseAtDecl(config.allow_decls); }, + .def_k => { + return self.parseDefDecl(.{ .attrs = &.{}, .hidden = false, .allow_decl = config.allow_decls}); + }, .pound => { self.advance(); if (self.peek().tag() != .ident) { return self.reportError("Unsupported compile-time statement.", &.{}); } + const token = self.peek(); + const name = self.ast.src[token.pos()..token.data.end_pos]; + _ = name; + const ident = try self.newSpanNode(.ident, self.next_pos); self.advance(); @@ -2042,6 +2130,8 @@ pub const Parser = struct { const attrs: []*ast.Attribute = @ptrCast(try self.ast.dupeNodes(&.{attr})); if (self.peek().tag() == .func_k) { return self.parseFuncDecl(.{ .hidden = hidden, .attrs = attrs, .allow_decl = allow_decls }); + } else if (self.peek().tag() == .def_k) { + return self.parseDefDecl(.{ .attrs = attrs, .hidden = hidden, .allow_decl = allow_decls}); } else if (self.peek().tag() == .var_k) { return try self.parseVarDecl(.{ .hidden = hidden, .attrs = attrs, .typed = true, .allow_static = allow_decls }); } else if (self.peek().tag() == .dyn_k) { @@ -2929,7 +3019,7 @@ pub const Parser = struct { } else if (tag == .comma) { self.advance(); const param = try self.genDynFuncParam(expr); - const params = try self.parseFuncParams(&.{param}, false); + const params = try self.parseFuncParams(&.{param}); return @ptrCast(try self.parseInferLambda(params)); } else { return self.reportError("Expected right parenthesis.", &.{}); @@ -3191,7 +3281,7 @@ pub const Parser = struct { } else if (self.peek().tag() == .comma) { self.advance(); const param = try self.genDynFuncParam(expr); - const params = try self.parseFuncParams(&.{ param }, false); + const params = try self.parseFuncParams(&.{ param }); if (self.peek().tag() == .equal_right_angle) { return @ptrCast(try self.parseInferLambda(params)); } else if (self.peek().tag() == .colon) { @@ -3723,7 +3813,7 @@ fn toBinExprOp(op: cy.tokenizer.TokenType) ?cy.ast.BinaryExprOp { .as_k, .at, .await_k, .bang, .bin, .break_k, .minus_right_angle, .case_k, .catch_k, .coinit_k, .colon, .comma, .context_k, .continue_k, .coresume_k, .coyield_k, .cstruct_k, - .dec, .dot, .dot_bang, .dot_question, .dot_dot, .dot_star, + .dec, .def_k, .dot, .dot_bang, .dot_question, .dot_dot, .dot_star, .else_k, .enum_k, .err, .error_k, .equal, .equal_right_angle, .false_k, .float, .for_k, .func_k, .Func_k, .hex, .ident, .if_k, .mod_k, .indent, diff --git a/src/sema.zig b/src/sema.zig index 24cbb8f4d..0df64757c 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -1209,23 +1209,23 @@ fn semaAccessFieldName(c: *cy.Chunk, rec: ExprResult, name: []const u8, field: * return c.reportErrorFmt("Type `{}` does not have a field named `{}`.", &.{v(type_name), v(name)}, field); } const field_sym = sym.cast(.field); + return semaField(c, rec, field_sym.idx, field_sym.type, field); +} +fn semaField(c: *cy.Chunk, rec: ExprResult, idx: usize, type_id: cy.TypeId, node: *ast.Node) !ExprResult { + var final_rec = rec; const rec_te = c.sema.getType(rec.type.id); if (rec_te.kind == .struct_t) { if (rec.resType != .local) { - const tempv = try declareHiddenLocal(c, "$temp", rec.type.id, rec, field); - const temp = try semaLocal(c, tempv.id, field); - return semaField(c, temp, field_sym.idx, field_sym.type, field); + const tempv = try declareHiddenLocal(c, "$temp", rec.type.id, rec, node); + const temp = try semaLocal(c, tempv.id, node); + final_rec = temp; } } - return semaField(c, rec, field_sym.idx, field_sym.type, field); -} - -fn semaField(c: *cy.Chunk, rec: ExprResult, idx: usize, type_id: cy.TypeId, node: *ast.Node) !ExprResult { const loc = try c.ir.pushExpr(.field, c.alloc, type_id, node, .{ .idx = @intCast(idx), - .rec = rec.irIdx, - .parent_t = rec.type.id, + .rec = final_rec.irIdx, + .parent_t = final_rec.type.id, }); return ExprResult.initCustom(loc, .field, CompactType.init(type_id), undefined); } @@ -1263,6 +1263,24 @@ const InitFieldResult = struct { idx: u8, }; +pub fn reserveFuncTemplate(c: *cy.Chunk, decl: *ast.TemplateDecl) !*cy.sym.FuncTemplate { + // Verify that template params do not have types. + const tparams = try c.alloc.alloc(cy.sym.FuncTemplateParam, decl.params.len); + for (decl.params, 0..) |param, i| { + if (param.type != null) { + return c.reportErrorFmt("Expected parameter type to be declared in function signature.", &.{}, @ptrCast(param.type)); + } + tparams[i] = .{ + .name = c.ast.nodeString(param.name_type), + .decl_idx = cy.NullId, + .infer = false, + }; + } + const func_decl = decl.child_decl.cast(.funcDecl); + const decl_path = try ensureDeclNamePath(c, @ptrCast(c.sym), func_decl.name); + return c.reserveFuncTemplate(decl_path.parent, decl_path.name.base_name, tparams, decl); +} + pub fn reserveTemplate(c: *cy.Chunk, node: *ast.TemplateDecl) !*cy.sym.Template { var name_n: *ast.Node = undefined; var template_t: cy.sym.TemplateType = undefined; @@ -1289,7 +1307,7 @@ pub fn reserveTemplate(c: *cy.Chunk, node: *ast.TemplateDecl) !*cy.sym.Template }, .funcDecl => { name_n = node.child_decl.cast(.funcDecl).name; - template_t = .ct_func; + template_t = .value; if (ast.findAttr(node.getAttrs(), .host) != null) { return c.reportError("A value template can not be binded to a `@host` function. Consider invoking a `@host` function in the template body instead.", @ptrCast(node)); @@ -1304,6 +1322,70 @@ pub fn reserveTemplate(c: *cy.Chunk, node: *ast.TemplateDecl) !*cy.sym.Template return c.reserveTemplate(decl_path.parent, decl_path.name.base_name, template_t, node); } +pub fn resolveFuncTemplate(c: *cy.Chunk, template: *cy.sym.FuncTemplate) !void { + try pushResolveContext(c); + defer popResolveContext(c); + + // Determine where each template param is declared in the function signature and which are inferrable. + for (template.func_params, 0..) |param, idx| { + const name = c.ast.nodeString(param.name_type); + if (std.mem.eql(u8, "self", name)) { + continue; + } + if (template.indexOfParam(name)) |tidx| { + if (template.params[tidx].decl_idx != cy.NullId) { + return c.reportErrorFmt("Function template parameter `{}` is already declared.", &.{v(name)}, @ptrCast(param.name_type)); + } + template.params[tidx] = .{ + .name = name, + .decl_idx = @intCast(idx), + .infer = false, + }; + param.sema_tparam = true; + continue; + } + try resolveFuncTemplateParamDeep(c, template, param.type.?, idx); + } + template.resolved = true; +} + +/// TODO: Turn this into an AST walker like the IR walker. +fn resolveFuncTemplateParamDeep(c: *cy.Chunk, template: *cy.sym.FuncTemplate, node: *ast.Node, fidx: usize) !void { + switch (node.type()) { + .ident => { + const name = c.ast.nodeString(node); + if (template.indexOfParam(name)) |tidx| { + if (template.params[tidx].decl_idx == cy.NullId) { + template.params[tidx].decl_idx = @intCast(fidx); + template.params[tidx].infer = true; + template.func_params[fidx].sema_infer_tparam = true; + } + } + }, + .ref_slice => { + const ref_slice = node.cast(.ref_slice); + try resolveFuncTemplateParamDeep(c, template, ref_slice.elem, fidx); + }, + .ptr_slice => { + const ptr_slice = node.cast(.ptr_slice); + try resolveFuncTemplateParamDeep(c, template, ptr_slice.elem, fidx); + }, + .ptr => { + const ptr = node.cast(.ptr); + try resolveFuncTemplateParamDeep(c, template, ptr.elem, fidx); + }, + .array_expr => { + const array_expr = node.cast(.array_expr); + for (array_expr.args) |arg| { + try resolveFuncTemplateParamDeep(c, template, arg, fidx); + } + }, + else => { + return c.reportErrorFmt("Unsupported node type `{}`.", &.{v(node.type())}, node); + } + } +} + pub fn resolveTemplate(c: *cy.Chunk, sym: *cy.sym.Template) !void { try pushResolveContext(c); getResolveContext(c).has_ct_params = true; @@ -1312,7 +1394,7 @@ pub fn resolveTemplate(c: *cy.Chunk, sym: *cy.sym.Template) !void { var sigId: FuncSigId = undefined; var ret: ?*ast.Node = null; - if (sym.kind == .ct_func) { + if (sym.kind == .value) { ret = sym.decl.child_decl.cast(.funcDecl).ret; } @@ -1670,12 +1752,6 @@ pub const ResolveContext = struct { type: ResolveContextType, has_ct_params: bool, - /// Interpret ct exprs as inferred ct params. - parse_ct_inferred_params: bool = false, - - /// CT exprs are expanded. - expand_ct_inferred_params: bool = false, - has_parent_ctx: bool = false, /// Prefer the comptime type when encountering a runtime type. (e.g. For template param types) @@ -1690,7 +1766,7 @@ pub const ResolveContext = struct { func: *cy.Func, }, - fn deinit(self: *ResolveContext, c: *cy.Chunk) void { + pub fn deinit(self: *ResolveContext, c: *cy.Chunk) void { var iter = self.ct_params.iterator(); while (iter.next()) |e| { c.vm.release(e.value_ptr.*); @@ -1698,8 +1774,8 @@ pub const ResolveContext = struct { self.ct_params.deinit(c.alloc); } - fn setCtParam(self: *ResolveContext, c: *cy.Chunk, name: []const u8, val: cy.Value) !void { - const res = try self.ct_params.getOrPut(c.alloc, name); + pub fn setCtParam(self: *ResolveContext, alloc: std.mem.Allocator, name: []const u8, val: cy.Value) !void { + const res = try self.ct_params.getOrPut(alloc, name); if (res.found_existing) { return error.DuplicateParam; } @@ -1739,7 +1815,7 @@ pub fn pushVariantResolveContext(c: *cy.Chunk, variant: *cy.sym.Variant) !void { for (variant.data.func.template.params, 0..) |param, i| { const arg = variant.args[i]; c.vm.retain(arg); - try new.setCtParam(c, param.name, arg); + try new.setCtParam(c.alloc, param.name, arg); } try c.resolve_stack.append(c.alloc, new); }, @@ -1750,7 +1826,7 @@ pub fn setContextTemplateParams(c: *cy.Chunk, ctx: *ResolveContext, template: *c for (template.params, 0..) |param, i| { const arg = args[i]; c.vm.retain(arg); - try ctx.setCtParam(c, param.name, arg); + try ctx.setCtParam(c.alloc, param.name, arg); } } @@ -1784,8 +1860,12 @@ pub fn pushResolveContext(c: *cy.Chunk) !void { try c.resolve_stack.append(c.alloc, new); } +pub fn pushSavedResolveContext(c: *cy.Chunk, ctx: ResolveContext) !void { + try c.resolve_stack.append(c.alloc, ctx); +} + pub fn setResolveCtParam(c: *cy.Chunk, name: []const u8, param: cy.Value) !void { - try c.resolve_stack.items[c.resolve_stack.items.len-1].setCtParam(c, name, param); + try c.resolve_stack.items[c.resolve_stack.items.len-1].setCtParam(c.alloc, name, param); } pub fn popResolveContext(c: *cy.Chunk) void { @@ -1794,21 +1874,27 @@ pub fn popResolveContext(c: *cy.Chunk) void { c.resolve_stack.items.len -= 1; } +pub fn saveResolveContext(c: *cy.Chunk) ResolveContext { + const last = c.resolve_stack.items[c.resolve_stack.items.len-1]; + c.resolve_stack.items.len -= 1; + return last; +} + /// Allow an explicit `opt_header_decl` so that template specialization can use it to override @host attributes. -pub fn reserveFuncTemplateVariant(c: *cy.Chunk, tfunc: *cy.Func, opt_header_decl: ?*ast.Node, variant: *cy.sym.Variant) !*cy.sym.Func { +pub fn reserveFuncTemplateVariant(c: *cy.Chunk, template: *cy.sym.FuncTemplate, opt_header_decl: ?*ast.Node, variant: *cy.sym.Variant) !*cy.sym.Func { var func: *cy.Func = undefined; - const decl = tfunc.decl.?.cast(.funcDecl); + const decl = template.decl.child_decl.cast(.funcDecl); + const is_method = c.ast.isMethodDecl(decl); if (decl.stmts.len == 0) { - func = try c.createFunc(.hostFunc, tfunc.parent, @ptrCast(opt_header_decl), tfunc.isMethod()); + func = try c.createFunc(.hostFunc, @ptrCast(template), @ptrCast(opt_header_decl), is_method); func.data = .{ .hostFunc = .{ .ptr = undefined, }}; } else { - func = try c.createFunc(.userFunc, tfunc.parent, @ptrCast(opt_header_decl), tfunc.isMethod()); + func = try c.createFunc(.userFunc, @ptrCast(template), @ptrCast(opt_header_decl), is_method); } - func.is_nested = tfunc.is_nested; + func.is_nested = false; func.variant = variant; - return func; } @@ -1875,7 +1961,7 @@ pub fn reserveTemplateVariant(c: *cy.Chunk, template: *cy.sym.Template, opt_head return @ptrCast(sym); }, - .ct_func => { + .value => { return error.Unexpected; }, } @@ -1885,7 +1971,7 @@ pub fn reserveTemplateVariant(c: *cy.Chunk, template: *cy.sym.Template, opt_head pub fn resolveTemplateVariant(c: *cy.Chunk, template: *cy.sym.Template, sym: *cy.Sym) anyerror!*cy.Sym { // Skip resolving if it's a compile-time infer type. const type_e = c.sema.types.items[sym.getStaticType().?]; - if (type_e.info.ct_infer or type_e.info.ct_ref) { + if (type_e.info.ct_ref) { return sym; } const tchunk = template.chunk(); @@ -2368,7 +2454,6 @@ pub fn resolveFuncVariant(c: *cy.Chunk, func: *cy.Func) !void { return error.TODO; // try sema.resolveImplicitTraitMethod(c, func); }, - .template, .userLambda => {}, } } @@ -2381,18 +2466,11 @@ pub fn resolveFunc2(c: *cy.Chunk, func: *cy.Func, has_parent_ctx: bool) !void { try pushFuncResolveContext(c, func); defer popResolveContext(c); getResolveContext(c).has_parent_ctx = has_parent_ctx; - getResolveContext(c).parse_ct_inferred_params = true; - const sig_id = try resolveFuncSig(c, func, false); - const sig = c.sema.getFuncSig(sig_id); - if (sig.info.is_template) { - const ct_params = getResolveContext(c).ct_params; - try resolveToFuncTemplate(c, func, sig_id, ct_params); - return; - } + const sig_id = try resolveFuncSig(c, func); switch (func.type) { .hostFunc => { - try resolveHostFunc2(c, func, sig_id); + try resolveHostFunc(c, func, sig_id); }, .userFunc => { try c.resolveUserFunc(func, sig_id); @@ -2413,7 +2491,6 @@ pub fn resolveFunc(c: *cy.Chunk, func: *cy.Func) !void { .hostFunc => { try resolveFunc2(c, func, false); }, - .template, .userLambda => {}, } } @@ -2431,32 +2508,16 @@ pub fn getHostAttrName(c: *cy.Chunk, attr: *ast.Attribute) !?[]const u8 { pub fn resolveHostFuncVariant(c: *cy.Chunk, func: *cy.Func) !void { try pushVariantResolveContext(c, func.variant.?); defer popResolveContext(c); - getResolveContext(c).expand_ct_inferred_params = true; - const sig_id = try resolveFuncSig(c, func, true); - try resolveHostFunc2(c, func, sig_id); + const sig_id = try resolveFuncSig(c, func); + const decl = func.decl.?.cast(.funcDecl); + const attr = ast.findAttr(decl.attrs, .host).?; + const func_ptr = try resolveHostFuncPtr(c, func, sig_id, attr); + func.data.hostFunc.ptr = @ptrCast(func_ptr); + c.resolveFuncInfo(func, sig_id); try c.deferred_funcs.append(c.alloc, func); } -pub fn resolveHostFunc(c: *cy.Chunk, func: *cy.Func) !void { - if (func.isResolved()) { - return; - } - - try pushResolveContext(c); - defer popResolveContext(c); - getResolveContext(c).parse_ct_inferred_params = true; - - const sig_id = try resolveFuncSig(c, func, false); - const sig = c.sema.getFuncSig(sig_id); - if (sig.is_template) { - const ct_params = getResolveContext(c).ct_params; - try resolveToFuncTemplate(c, func, sig_id, ct_params); - } else { - try resolveHostFunc2(c, func, sig_id); - } -} - pub fn resolveHostFuncPtr(c: *cy.Chunk, func: *cy.Func, func_sig: FuncSigId, host_attr: *ast.Attribute) !C.FuncFn { const host_name = try getHostAttrName(c, host_attr); var name_buf: [128]u8 = undefined; @@ -2487,7 +2548,7 @@ pub fn resolveHostFuncPtr(c: *cy.Chunk, func: *cy.Func, func_sig: FuncSigId, hos return res; } -fn resolveHostFunc2(c: *cy.Chunk, func: *cy.Func, func_sig: FuncSigId) !void { +fn resolveHostFunc(c: *cy.Chunk, func: *cy.Func, func_sig: FuncSigId) !void { const decl = func.decl.?.cast(.funcDecl); const attr = ast.findAttr(decl.attrs, .host).?; const func_ptr = try resolveHostFuncPtr(c, func, func_sig, attr); @@ -2508,28 +2569,12 @@ pub fn reserveUserFunc2(c: *cy.Chunk, parent: *cy.Sym, name: []const u8, decl: * pub fn resolveUserFuncVariant(c: *cy.Chunk, func: *cy.Func) !void { try pushVariantResolveContext(c, func.variant.?); defer popResolveContext(c); - getResolveContext(c).expand_ct_inferred_params = true; - const sig_id = try resolveFuncSig(c, func, true); + const sig_id = try resolveFuncSig(c, func); c.resolveFuncInfo(func, sig_id); try c.deferred_funcs.append(c.alloc, func); } -pub fn resolveUserFunc(c: *cy.Chunk, func: *cy.Func) !void { - try pushResolveContext(c); - defer popResolveContext(c); - getResolveContext(c).parse_ct_inferred_params = true; - - const sig_id = try resolveFuncSig(c, func, false); - const sig = c.sema.getFuncSig(sig_id); - if (sig.is_template) { - const ct_params = getResolveContext(c).ct_params; - try resolveToFuncTemplate(c, func, sig_id, ct_params); - } else { - try c.resolveUserFunc(func, sig_id); - } -} - pub fn reserveImplicitTraitMethod(c: *cy.Chunk, parent: *cy.Sym, decl: *ast.FuncDecl, vtable_idx: usize, is_variant: bool) !*cy.Func { const decl_path = try ensureDeclNamePath(c, parent, decl.name); if (decl.stmts.len > 0) { @@ -2559,42 +2604,6 @@ pub fn reserveNestedFunc(c: *cy.Chunk, parent: *cy.Sym, decl: *ast.FuncDecl, def return c.reportErrorFmt("`{}` does not have an initializer.", &.{v(name)}, @ptrCast(decl)); } -fn resolveToFuncTemplate(c: *cy.Chunk, func: *cy.Func, sig_id: FuncSigId, ct_params: std.StringHashMapUnmanaged(cy.Value)) !void { - func.type = .template; - - const typeStart = c.typeStack.items.len; - defer c.typeStack.items.len = typeStart; - - const template = try c.alloc.create(cy.sym.FuncTemplate); - const params = try c.alloc.alloc(cy.sym.FuncTemplateParam, ct_params.size); - var iter = ct_params.iterator(); - while (iter.next()) |e| { - const param = e.value_ptr.*; - if (param.getTypeId() != bt.Type) { - return error.Unexpected; - } - const type_e = c.sema.types.items[param.asHeapObject().type.type]; - if (type_e.kind != .ct_ref) { - return error.Unexpected; - } - params[type_e.data.ct_ref.ct_param_idx] = .{ - .name = e.key_ptr.*, - }; - try c.typeStack.append(c.alloc, param.getTypeId()); - } - - const retType = bt.Any; - const sig = try c.sema.ensureFuncSig(@ptrCast(c.typeStack.items[typeStart..]), retType); - - template.* = .{ - .sig = sig, - .params = params, - .variant_cache = .{}, - .variants = .{}, - }; - try c.resolveTemplateFunc(func, sig_id, template); -} - pub fn methodDecl(c: *cy.Chunk, func: *cy.Func) !void { if (func.variant) |variant| { try pushVariantResolveContext(c, variant); @@ -3169,9 +3178,15 @@ fn requireFuncSym(c: *cy.Chunk, sym: *Sym, node: *ast.Node) !*cy.sym.FuncSym { // Invoke a type's sym as the callee. fn callNamespaceSym(c: *cy.Chunk, sym: *Sym, sym_n: *ast.Node, args: []*ast.Node, ret_cstr: ReturnCstr, node: *ast.Node) !ExprResult { const call_sym = try c.mustFindSym(sym, "$call", sym_n); - const func_sym = try requireFuncSym(c, call_sym, sym_n); - const cstr = sema.CallCstr{ .ret = ret_cstr }; - return c.semaCallFuncSym(func_sym, args, cstr, node); + if (call_sym.type == .func) { + const cstr = sema.CallCstr{ .ret = ret_cstr }; + return c.semaCallFuncSym(call_sym.cast(.func), args, cstr, node); + } else if (call_sym.type == .func_template) { + const cstr = sema.CallCstr{ .ret = ret_cstr }; + return c.semaCallFuncTemplate(call_sym.cast(.func_template), args, cstr, node); + } else { + return c.reportErrorFmt("Expected `{}` to be a function.", &.{v(sym.name())}, node); + } } fn callSym(c: *cy.Chunk, sym: *Sym, symNode: *ast.Node, args: []*ast.Node, cstr: sema.CallCstr, node: *ast.Node) !ExprResult { @@ -3181,6 +3196,10 @@ fn callSym(c: *cy.Chunk, sym: *Sym, symNode: *ast.Node, args: []*ast.Node, cstr: const funcSym = sym.cast(.func); return c.semaCallFuncSym(funcSym, args, cstr, symNode); }, + .func_template => { + const template = sym.cast(.func_template); + return c.semaCallFuncTemplate(template, args, cstr, symNode); + }, .trait_t, .enum_t, .type, @@ -3210,8 +3229,7 @@ fn callSym(c: *cy.Chunk, sym: *Sym, symNode: *ast.Node, args: []*ast.Node, cstr: }, else => { // try pushCallArgs(c, node.data.callExpr.argHead, numArgs, true); - log.tracev("{}", .{sym.type}); - return error.TODO; + return c.reportErrorFmt("Unsupported call from symbol: `{}`", &.{v(sym.type)}, symNode); }, } } @@ -3285,6 +3303,7 @@ pub fn symbol(c: *cy.Chunk, sym: *Sym, expr: Expr, prefer_ct_sym: bool) !ExprRes } }, .chunk, + .func_template, .template => { if (prefer_ct_sym) { const ctype = CompactType.init(bt.Void); @@ -3511,6 +3530,13 @@ pub fn getResolvedSym(c: *cy.Chunk, name: []const u8, node: *ast.Node, distinct: return null; } +pub fn ensureResolvedFuncTemplate(c: *cy.Chunk, template: *cy.sym.FuncTemplate) !void { + if (template.isResolved()) { + return; + } + try sema.resolveFuncTemplate(c, template); +} + pub fn ensureResolvedTemplate(c: *cy.Chunk, template: *cy.sym.Template) !void { if (template.isResolved()) { return; @@ -3580,13 +3606,9 @@ pub fn resolveTypeSpecNode(c: *cy.Chunk, node: ?*ast.Node) anyerror!cy.TypeId { return type_id; } -pub fn resolveReturnTypeSpecNode(c: *cy.Chunk, node: ?*ast.Node, typed: bool) anyerror!cy.TypeId { +pub fn resolveReturnTypeSpecNode(c: *cy.Chunk, node: ?*ast.Node) anyerror!cy.TypeId { const n = node orelse { - if (typed) { - return bt.Void; - } else { - return bt.Dyn; - } + return bt.Void; }; return cte.resolveCtType(c, n); } @@ -3636,7 +3658,7 @@ fn resolveTemplateSig(c: *cy.Chunk, params: []*ast.FuncParam, opt_ret: ?*ast.Nod var ret_t = bt.Type; if (opt_ret) |ret| { - ret_t = try resolveReturnTypeSpecNode(c, ret, true); + ret_t = try resolveReturnTypeSpecNode(c, ret); } outSigId.* = try c.sema.ensureFuncSig(@ptrCast(c.typeStack.items[typeStart..]), ret_t); @@ -3648,9 +3670,6 @@ pub fn resolveFuncType(c: *cy.Chunk, func_type: *ast.FuncType) !FuncSigId { defer c.typeStack.items.len = start; for (func_type.params) |param| { - if (param.template) { - return error.TODO; - } var type_id: cy.TypeId = undefined; if (param.type) |type_spec| { type_id = try resolveTypeSpecNode(c, type_spec); @@ -3658,16 +3677,16 @@ pub fn resolveFuncType(c: *cy.Chunk, func_type: *ast.FuncType) !FuncSigId { type_id = try resolveTypeSpecNode(c, param.name_type); } - const param_t = FuncParam.init(type_id, false); + const param_t = FuncParam.init(type_id); try c.typeStack.append(c.alloc, @bitCast(param_t)); } // Get return type. - const ret_t = try resolveReturnTypeSpecNode(c, func_type.ret, true); + const ret_t = try resolveReturnTypeSpecNode(c, func_type.ret); return c.sema.ensureFuncSig(@ptrCast(c.typeStack.items[start..]), ret_t); } -fn indexOfTypedParam(params: []const *ast.FuncParam, start: usize) ?usize { +pub fn indexOfTypedParam(params: []const *ast.FuncParam, start: usize) ?usize { for (params[start..], start..) |param, i| { if (param.type != null) { return i; @@ -3677,7 +3696,7 @@ fn indexOfTypedParam(params: []const *ast.FuncParam, start: usize) ?usize { } /// `skip_ct_params` is used when expanding a template. -fn resolveFuncSig(c: *cy.Chunk, func: *cy.Func, skip_ct_params: bool) !FuncSigId { +fn resolveFuncSig(c: *cy.Chunk, func: *cy.Func) !FuncSigId { const func_n = func.decl.?.cast(.funcDecl); // Get params, build func signature. @@ -3697,6 +3716,9 @@ fn resolveFuncSig(c: *cy.Chunk, func: *cy.Func, skip_ct_params: bool) !FuncSigId const self_t = func.parent.parent.?.getStaticType().?; try c.typeStack.append(c.alloc, self_t); } else { + if (param.sema_tparam) { + continue; + } var type_id: cy.TypeId = undefined; if (param.type == null) { if (param_group_t == cy.NullId or i > param_group_end) { @@ -3711,23 +3733,13 @@ fn resolveFuncSig(c: *cy.Chunk, func: *cy.Func, skip_ct_params: bool) !FuncSigId type_id = try resolveTypeSpecNode(c, param.type); } - if (param.template) { - if (skip_ct_params) { - continue; - } - const ct_param_idx = getResolveContext(c).ct_params.size; - const ref_t = try c.sema.ensureCtRefType(ct_param_idx); - const param_v = try c.vm.allocType(ref_t); - try setResolveCtParam(c, paramName, param_v); - } - - const param_t = FuncParam.init(type_id, param.template); + const param_t = FuncParam.init(type_id); try c.typeStack.append(c.alloc, @bitCast(param_t)); } } // Get return type. - const retType = try resolveReturnTypeSpecNode(c, func_n.ret, sig_t == .func); + const retType = try resolveReturnTypeSpecNode(c, func_n.ret); return c.sema.ensureFuncSig(@ptrCast(c.typeStack.items[start..]), retType); } @@ -4031,7 +4043,7 @@ fn pushMethodParamVars(c: *cy.Chunk, objectT: TypeId, func: *const cy.Func) !voi } var rt_param_idx: usize = 1; for (rest) |param_decl| { - if (param_decl.template) { + if (param_decl.sema_tparam) { continue; } try declareParam(c, param_decl, false, rt_param_idx, params[rt_param_idx].type); @@ -4047,7 +4059,7 @@ fn appendFuncParamVars(c: *cy.Chunk, ast_params: []const *ast.FuncParam, params: if (params.len > 0) { var rt_param_idx: usize = 0; for (ast_params) |param| { - if (param.template) { + if (param.sema_tparam) { continue; } try declareParam(c, param, false, rt_param_idx, params[rt_param_idx].type); @@ -4276,14 +4288,22 @@ fn lookupParentLocal(c: *cy.Chunk, name: []const u8) !?LookupParentLocalResult { return null; } -pub fn reportIncompatCallFunc(c: *cy.Chunk, func: *cy.Func, args: []const cy.TypeId, ret_cstr: ReturnCstr, node: *ast.Node) anyerror { +pub fn reportIncompatType(c: *cy.Chunk, exp_t: cy.TypeId, act_t: cy.TypeId, node: *ast.Node) anyerror { + const exp_name = try c.sema.allocTypeName(exp_t); + defer c.alloc.free(exp_name); + const act_name = try c.sema.allocTypeName(act_t); + defer c.alloc.free(act_name); + return c.reportErrorFmt("Expected type `{}`. Found `{}`.", &.{v(exp_name), v(act_name)}, node); +} + +pub fn reportIncompatCallFunc(c: *cy.Chunk, func: *cy.Func, args: []const cy.TypeId, ret_cstr: ReturnCstr, node: *ast.Node) anyerror { const name = func.name(); var msg: std.ArrayListUnmanaged(u8) = .{}; const w = msg.writer(c.alloc); const callSigStr = try c.sema.allocTypesStr(args, c); defer c.alloc.free(callSigStr); - try w.print("Can not find compatible function for call: `{s}{s}`.", .{name, callSigStr}); + try w.print("Can not find compatible function for call: `{s}({s})`.", .{name, callSigStr}); if (ret_cstr == .not_void) { try w.writeAll(" Expects non-void return."); } @@ -4305,7 +4325,7 @@ pub fn reportIncompatCallFuncSym(c: *cy.Chunk, sym: *cy.sym.FuncSym, args: []con const callSigStr = try c.sema.allocTypesStr(args, c); defer c.alloc.free(callSigStr); - try w.print("Can not find compatible function for call: `{s}{s}`.", .{name, callSigStr}); + try w.print("Can not find compatible function for call: `{s}({s})`.", .{name, callSigStr}); if (ret_cstr == .not_void) { try w.writeAll(" Expects non-void return."); } @@ -5107,6 +5127,31 @@ pub const ChunkExt = struct { return c.semaCallFuncSymResult(sym, res, cstr.ct_call, node); } + pub fn semaCallFuncTemplateRec(c: *cy.Chunk, template: *cy.sym.FuncTemplate, rec: *ast.Node, rec_res: ExprResult, + args: []const *ast.Node, ret_cstr: ReturnCstr, node: *ast.Node) !ExprResult { + + const arg_start = c.arg_stack.items.len; + defer c.arg_stack.items.len = arg_start; + + try c.arg_stack.append(c.alloc, sema.Argument.initPreResolved(rec, rec_res)); + for (args) |arg| { + try c.arg_stack.append(c.alloc, sema.Argument.init(arg)); + } + const cstr = sema.CallCstr{ .ret = ret_cstr }; + const res = try sema.matchFuncTemplate(c, template, arg_start, args.len+1, cstr, node); + return c.semaCallFuncResult(res, node); + } + + pub fn semaCallFuncTemplate(c: *cy.Chunk, template: *cy.sym.FuncTemplate, args: []*ast.Node, cstr: sema.CallCstr, node: *ast.Node) !ExprResult { + const arg_start = c.arg_stack.items.len; + defer c.arg_stack.items.len = arg_start; + for (args) |arg| { + try c.arg_stack.append(c.alloc, sema.Argument.init(arg)); + } + const res = try sema.matchFuncTemplate(c, template, arg_start, args.len, cstr, node); + return c.semaCallFuncResult(res, node); + } + pub fn semaPushDynCallArgs(c: *cy.Chunk, args: []*ast.Node) !u32 { const loc = try c.ir.pushEmptyArray(c.alloc, u32, args.len); for (args, 0..) |arg, i| { @@ -5131,8 +5176,8 @@ pub const ChunkExt = struct { if (left.resType == .sym) { if (left.data.sym.type == .template) { const template = left.data.sym.cast(.template); - if (template.kind == .ct_func) { - const ct_val = try cte.expandCtFuncTemplateOnCallArgs(c, template, array_expr.args, node); + if (template.kind == .value) { + const ct_val = try cte.expandValueTemplateForArgs(c, template, array_expr.args, node); defer c.vm.release(ct_val.value); if (ct_val.value.getTypeId() == bt.Type) { const final_sym = c.sema.getTypeSym(ct_val.value.castHeapObject(*cy.heap.Type).type); @@ -5141,18 +5186,13 @@ pub const ChunkExt = struct { return c.reportErrorFmt("Expected symbol.", &.{}, node); } } else { - const final_sym = try cte.expandTemplateOnCallArgs(c, template, array_expr.args, node); + const final_sym = try cte.expandTemplateForArgs(c, template, array_expr.args, node); return sema.symbol(c, final_sym, expr, true); } - } else if (left.data.sym.type == .func) { - const func_sym = left.data.sym.cast(.func); - if (func_sym.numFuncs == 1) { - const func = func_sym.first; - if (func.type == .template) { - const func_res = try cte.expandFuncTemplateOnCallArgs(c, func, array_expr.args, node); - return ExprResult.initCustom(cy.NullId, .func, CompactType.init(bt.Void), .{ .func = func_res }); - } - } + } else if (left.data.sym.type == .func_template) { + const template = left.data.sym.cast(.func_template); + const func_res = try cte.expandFuncTemplateForArgs(c, template, array_expr.args, node); + return ExprResult.initCustom(cy.NullId, .func, CompactType.init(bt.Void), .{ .func = func_res }); } left = try sema.symbol(c, left.data.sym, expr, false); } @@ -5174,19 +5214,19 @@ pub const ChunkExt = struct { }, .array_type => { const array_type = node.cast(.array_type); - const sym = try cte.expandTemplateOnCallArgs(c, c.sema.array_tmpl, &.{ array_type.size, array_type.elem }, node); + const sym = try cte.expandTemplateForArgs(c, c.sema.array_tmpl, &.{ array_type.size, array_type.elem }, node); const ctype = CompactType.init(sym.getStaticType().?); return ExprResult.initCustom(cy.NullId, .sym, ctype, .{ .sym = sym }); }, .expandOpt => { const expand_opt = node.cast(.expandOpt); - const sym = try cte.expandTemplateOnCallArgs(c, c.sema.option_tmpl, &.{ expand_opt.param }, node); + const sym = try cte.expandTemplateForArgs(c, c.sema.option_tmpl, &.{ expand_opt.param }, node); const ctype = CompactType.init(sym.getStaticType().?); return ExprResult.initCustom(cy.NullId, .sym, ctype, .{ .sym = sym }); }, .ptr => { const ptr = node.cast(.ptr); - const sym = try cte.expandTemplateOnCallArgs(c, c.sema.pointer_tmpl, &.{ ptr.elem }, node); + const sym = try cte.expandTemplateForArgs(c, c.sema.pointer_tmpl, &.{ ptr.elem }, node); const ctype = CompactType.init(sym.getStaticType().?); return ExprResult.initCustom(cy.NullId, .sym, ctype, .{ .sym = sym }); }, @@ -5584,26 +5624,26 @@ pub const ChunkExt = struct { return res; }, .ptr_slice => { - const sym = try cte.expandTemplateOnCallArgs(c, c.sema.ptr_slice_tmpl, &.{node.cast(.ptr_slice).elem}, node); + const sym = try cte.expandTemplateForArgs(c, c.sema.ptr_slice_tmpl, &.{node.cast(.ptr_slice).elem}, node); const type_id = sym.getStaticType().?; const irIdx = try c.ir.pushExpr(.type, c.alloc, bt.Type, node, .{ .typeId = type_id }); return ExprResult.init(irIdx, CompactType.init(bt.Type)); }, .ref_slice => { - const sym = try cte.expandTemplateOnCallArgs(c, c.sema.ref_slice_tmpl, &.{node.cast(.ref_slice).elem}, node); + const sym = try cte.expandTemplateForArgs(c, c.sema.ref_slice_tmpl, &.{node.cast(.ref_slice).elem}, node); const type_id = sym.getStaticType().?; const irIdx = try c.ir.pushExpr(.type, c.alloc, bt.Type, node, .{ .typeId = type_id }); return ExprResult.init(irIdx, CompactType.init(bt.Type)); }, .expandOpt => { - const sym = try cte.expandTemplateOnCallArgs(c, c.sema.option_tmpl, &.{node.cast(.expandOpt).param}, node); + const sym = try cte.expandTemplateForArgs(c, c.sema.option_tmpl, &.{node.cast(.expandOpt).param}, node); const type_id = sym.getStaticType().?; const irIdx = try c.ir.pushExpr(.type, c.alloc, bt.Type, node, .{ .typeId = type_id }); return ExprResult.init(irIdx, CompactType.init(bt.Type)); }, .array_type => { const array_type = node.cast(.array_type); - const sym = try cte.expandTemplateOnCallArgs(c, c.sema.array_tmpl, &.{array_type.size, array_type.elem}, node); + const sym = try cte.expandTemplateForArgs(c, c.sema.array_tmpl, &.{array_type.size, array_type.elem}, node); const type_id = sym.getStaticType().?; const irIdx = try c.ir.pushExpr(.type, c.alloc, bt.Type, node, .{ .typeId = type_id }); return ExprResult.init(irIdx, CompactType.init(bt.Type)); @@ -5681,26 +5721,21 @@ pub const ChunkExt = struct { if (left.resType == .sym) { if (left.data.sym.type == .template) { const template = left.data.sym.cast(.template); - if (template.kind == .ct_func) { - const ct_val = try cte.expandCtFuncTemplateOnCallArgs(c, template, array_expr.args, node); + if (template.kind == .value) { + const ct_val = try cte.expandValueTemplateForArgs(c, template, array_expr.args, node); defer c.vm.release(ct_val.value); return semaCtValue(c, ct_val, expr, false); } else { - const final_sym = try cte.expandTemplateOnCallArgs(c, left.data.sym.cast(.template), array_expr.args, node); + const final_sym = try cte.expandTemplateForArgs(c, left.data.sym.cast(.template), array_expr.args, node); return sema.symbol(c, final_sym, expr, false); } - } else if (left.data.sym.type == .func) { - const func_sym = left.data.sym.cast(.func); - if (func_sym.numFuncs == 1) { - const func = func_sym.first; - if (func.type == .template) { - const func_res = try cte.expandFuncTemplateOnCallArgs(c, func, array_expr.args, node); - const typeId = try cy.sema.getFuncPtrType(c, func_res.funcSigId); - const ctype = CompactType.init(typeId); - const loc = try c.ir.pushExpr(.func_ptr, c.alloc, typeId, node, .{ .func = func_res }); - return ExprResult.initCustom(loc, .func, ctype, .{ .func = func_res }); - } - } + } else if (left.data.sym.type == .func_template) { + const template = left.data.sym.cast(.func_template); + const func_res = try cte.expandFuncTemplateForArgs(c, template, array_expr.args, node); + const typeId = try cy.sema.getFuncPtrType(c, func_res.funcSigId); + const ctype = CompactType.init(typeId); + const loc = try c.ir.pushExpr(.func_ptr, c.alloc, typeId, node, .{ .func = func_res }); + return ExprResult.initCustom(loc, .func, ctype, .{ .func = func_res }); } else { left = try sema.symbol(c, left.data.sym, expr, false); } @@ -5917,7 +5952,7 @@ pub const ChunkExt = struct { .expr = expr_res.irIdx, }); } else { - func_ret_t = try resolveReturnTypeSpecNode(c, lambda.ret, lambda.sig_t == .func); + func_ret_t = try resolveReturnTypeSpecNode(c, lambda.ret); const expr_res = try c.semaExprCstr(@ptrCast(@constCast(@alignCast(lambda.stmts.ptr))), func_ret_t); _ = try c.ir.pushStmt(c.alloc, .retExprStmt, node, .{ @@ -5969,7 +6004,7 @@ pub const ChunkExt = struct { _ = try pushLambdaProc(c, func); const irIdx = c.proc().irStart; - const func_ret_t = try resolveReturnTypeSpecNode(c, lambda.ret, lambda.sig_t == .func); + const func_ret_t = try resolveReturnTypeSpecNode(c, lambda.ret); const params = c.typeStack.items[start..]; const sig = try c.sema.ensureFuncSig(@ptrCast(params), func_ret_t); try c.resolveUserLambda(func, sig); @@ -6166,6 +6201,9 @@ pub const ChunkExt = struct { if (rightSym.type == .func) { return c.semaCallFuncSymRec(rightSym.cast(.func), callee.left, leftRes, node.args, expr.getRetCstr(), expr.node); + } else if (rightSym.type == .func_template) { + return c.semaCallFuncTemplateRec(rightSym.cast(.func_template), callee.left, leftRes, + node.args, expr.getRetCstr(), expr.node); } else { const callee_v = try c.semaExpr(node.callee, .{}); if (callee_v.type.isDynAny()) { @@ -6782,50 +6820,34 @@ fn semaStructCompare(c: *cy.Chunk, left: ExprResult, left_id: *ast.Node, op: cy. var field_t = c.sema.getTypeSym(fields[0].type); var field_te = c.sema.getType(fields[0].type); var it: u32 = undefined; - if (field_te.kind != .int and field_t.type != .object_t) { + if (field_te.kind == .struct_t) { return error.Unsupported; } else { - const left_f = try c.ir.pushExpr(.field, c.alloc, fields[0].type, left_id, .{ - .idx = 0, - .rec = left.irIdx, - .parent_t = left_t, - }); - const right_f = try c.ir.pushExpr(.field, c.alloc, fields[0].type, right_id, .{ - .idx = 0, - .rec = right.irIdx, - .parent_t = left_t, - }); + const left_f = try semaField(c, left, 0, fields[0].type, left_id); + const right_f = try semaField(c, right, 0, fields[0].type, right_id); it = try c.ir.pushExpr(.preBinOp, c.alloc, bt.Boolean, node_id, .{ .binOp = .{ .leftT = fields[0].type, .rightT = fields[0].type, .op = op, - .left = left_f, - .right = right_f, + .left = left_f.irIdx, + .right = right_f.irIdx, }}); } if (fields.len > 1) { for (fields[1..], 1..) |field, fidx| { field_t = c.sema.getTypeSym(fields[0].type); field_te = c.sema.getType(fields[0].type); - if (field_te.kind != .int and field_t.type != .object_t) { + if (field_te.kind == .struct_t) { return error.Unsupported; } - const left_f = try c.ir.pushExpr(.field, c.alloc, field.type, left_id, .{ - .idx = @as(u8, @intCast(fidx)), - .rec = left.irIdx, - .parent_t = left_t, - }); - const right_f = try c.ir.pushExpr(.field, c.alloc, field.type, right_id, .{ - .idx = @as(u8, @intCast(fidx)), - .rec = right.irIdx, - .parent_t = left_t, - }); + const left_f = try semaField(c, left, fidx, field.type, left_id); + const right_f = try semaField(c, right, fidx, field.type, right_id); const compare = try c.ir.pushExpr(.preBinOp, c.alloc, bt.Boolean, node_id, .{ .binOp = .{ .leftT = field.type, .rightT = field.type, .op = op, - .left = left_f, - .right = right_f, + .left = left_f.irIdx, + .right = right_f.irIdx, }}); const logic_op: cy.BinaryExprOp = if (op == .equal_equal) .and_op else .or_op; it = try c.ir.pushExpr(.preBinOp, c.alloc, bt.Boolean, node_id, .{ .binOp = .{ @@ -6998,17 +7020,10 @@ pub fn popFuncBlock(c: *cy.Chunk) !void { } pub const FuncParam = packed struct { - type: u31, - - /// Template param, does not include ct infer types or ct ref types. - template: bool, + type: u32, - pub fn init(type_id: cy.TypeId, template: bool) FuncParam { - return .{ .type = @intCast(type_id), .template = template }; - } - - pub fn initRt(type_id: cy.TypeId) FuncParam { - return .{ .type = @intCast(type_id), .template = false }; + pub fn init(type_id: cy.TypeId) FuncParam { + return .{ .type = @intCast(type_id) }; } }; @@ -7032,13 +7047,8 @@ pub const FuncSig = struct { /// Requires type checking if any param is not `dynamic` or `any`. reqCallTypeCheck: bool, - is_template: bool, - /// Contains a param or return type that is dependent on a compile-time param. ct_dep: bool, - - /// Contains a param that infers a compile-time param. - ct_infer: bool, }, pub inline fn params(self: FuncSig) []const FuncParam { @@ -7078,7 +7088,6 @@ pub const Sema = struct { /// Maps index to the ct_ref type. ct_ref_types: std.AutoHashMapUnmanaged(u32, cy.TypeId), - ct_infer_types: std.AutoHashMapUnmanaged(u32, cy.TypeId), /// Resolved signatures for functions. funcSigs: std.ArrayListUnmanaged(FuncSig), @@ -7122,7 +7131,6 @@ pub const Sema = struct { .func_ptr_types = .{}, .types = .{}, .ct_ref_types = .{}, - .ct_infer_types = .{}, }; // Reserve the null type. _ = try new.pushType(); @@ -7138,11 +7146,6 @@ pub const Sema = struct { const sym = self.types.items[e.value_ptr.*].sym.cast(.dummy_t); alloc.destroy(sym); } - iter = self.ct_infer_types.iterator(); - while (iter.next()) |e| { - const sym = self.types.items[e.value_ptr.*].sym.cast(.dummy_t); - alloc.destroy(sym); - } for (self.types.items) |type_e| { if (type_e.kind == .object) { @@ -7157,14 +7160,12 @@ pub const Sema = struct { } if (reset) { self.ct_ref_types.clearRetainingCapacity(); - self.ct_infer_types.clearRetainingCapacity(); self.types.items.len = 1; self.funcSigs.clearRetainingCapacity(); self.funcSigMap.clearRetainingCapacity(); self.func_ptr_types.clearRetainingCapacity(); } else { self.ct_ref_types.deinit(alloc); - self.ct_infer_types.deinit(alloc); self.types.deinit(alloc); self.funcSigs.deinit(alloc); self.funcSigMap.deinit(alloc); @@ -7172,25 +7173,6 @@ pub const Sema = struct { } } - pub fn ensureCtInferType(s: *Sema, ct_param_idx: u32) !cy.TypeId { - const res = try s.ct_infer_types.getOrPut(s.alloc, ct_param_idx); - if (!res.found_existing) { - const new_t = try s.pushType(); - s.types.items[new_t].kind = .ct_infer; - const sym = try s.alloc.create(cy.sym.DummyType); - sym.* = .{ - .head = cy.Sym.init(.dummy_t, null, "ct-infer"), - .type = new_t, - }; - s.types.items[new_t].sym = @ptrCast(sym); - s.types.items[new_t].info.ct_infer = true; - s.types.items[new_t].data = .{ .ct_infer = .{ .ct_param_idx = ct_param_idx }}; - - res.value_ptr.* = new_t; - } - return res.value_ptr.*; - } - pub fn ensureCtRefType(s: *Sema, ct_param_idx: u32) !cy.TypeId { const res = try s.ct_ref_types.getOrPut(s.alloc, ct_param_idx); if (!res.found_existing) { @@ -7213,7 +7195,7 @@ pub const Sema = struct { pub fn ensureUntypedFuncSig(s: *Sema, numParams: u32) !FuncSigId { const buf = std.mem.bytesAsSlice(FuncParam, &cy.tempBuf); if (buf.len < numParams) return error.TooBig; - @memset(buf[0..numParams], FuncParam.initRt(bt.Dyn)); + @memset(buf[0..numParams], FuncParam.init(bt.Dyn)); return try s.ensureFuncSig(buf[0..numParams], bt.Dyn); } @@ -7233,17 +7215,10 @@ pub const Sema = struct { const id: u32 = @intCast(s.funcSigs.items.len); const new = try s.alloc.dupe(FuncParam, params); var reqCallTypeCheck = false; - var is_template = false; - var ct_infer = false; var ct_dep = false; for (params) |param| { const param_te = s.getType(param.type); ct_dep = ct_dep or param_te.info.ct_ref; - ct_infer = ct_infer or param_te.info.ct_infer; - if (param.template or param_te.info.ct_infer) { - is_template = true; - continue; - } if (!reqCallTypeCheck and param.type != bt.Dyn and param.type != bt.Any) { reqCallTypeCheck = true; } @@ -7254,9 +7229,7 @@ pub const Sema = struct { .ret = ret, .info = .{ .reqCallTypeCheck = reqCallTypeCheck, - .is_template = is_template, .ct_dep = ct_dep, - .ct_infer = ct_infer, }, }); res.value_ptr.* = id; @@ -7309,7 +7282,6 @@ pub const Sema = struct { defer buf.deinit(s.alloc); const w = buf.writer(s.alloc); - try w.writeAll("("); if (types.len > 0) { try s.writeTypeName(w, types[0], from); @@ -7320,7 +7292,6 @@ pub const Sema = struct { } } } - try w.writeAll(")"); return buf.toOwnedSlice(s.alloc); } diff --git a/src/sema_func.zig b/src/sema_func.zig index 12c6dd564..880baf28d 100644 --- a/src/sema_func.zig +++ b/src/sema_func.zig @@ -5,6 +5,8 @@ const ast = cy.ast; const sema = cy.sema; const cte = cy.cte; const log = cy.log.scoped(.sema_func); +const fmt = cy.fmt; +const v = fmt.v; pub const FuncSymResult = struct { dyn_call: bool, @@ -51,7 +53,11 @@ pub const Argument = struct { res: union { rt: sema.ExprResult, ct: cte.CtValue, - incompat: cy.TypeId, + template: cte.CtValue, + incompat: struct { + target_t: cy.TypeId, + res_t: cy.TypeId, + }, }, node: *ast.Node, @@ -74,6 +80,118 @@ pub const Argument = struct { } }; +pub fn matchFuncTemplate(c: *cy.Chunk, template: *cy.sym.FuncTemplate, + arg_start: usize, nargs: usize, cstr: CallCstr, node: *ast.Node) !FuncResult { + + if (template.func_params.len != nargs) { + return c.reportErrorFmt("Expected {} arguments. Found {}.", &.{v(template.func_params.len), v(nargs)}, node); + } + + // Prepare template context. + const src_chunk = template.chunk(); + try sema.pushResolveContext(src_chunk); + var template_ctx = sema.saveResolveContext(src_chunk); + template_ctx.has_ct_params = true; + defer template_ctx.deinit(src_chunk); + + const loc = try c.ir.pushEmptyArray(c.alloc, u32, nargs); + + // Record resolved func parameter types, for error + const type_start = c.typeStack.items.len; + defer c.typeStack.items.len = type_start; + + // ct/rt args share the same index counter. + var rt_arg_idx: u32 = 0; + + const ct_arg_start = c.valueStack.items.len; + if (cstr.ct_call) { + // First reserve the ct args since the exact amount is known. + try c.valueStack.resize(c.alloc, ct_arg_start + nargs); + } + defer { + if (cstr.ct_call) { + for (c.valueStack.items[ct_arg_start..ct_arg_start+rt_arg_idx]) |arg| { + c.vm.release(arg); + } + } + c.valueStack.items.len = ct_arg_start; + } + + const config = MatchConfig{ + .template_arg_start = undefined, + .single_func = true, + .ct_call = cstr.ct_call, + }; + for (0..nargs) |i| { + const arg = c.arg_stack.items[arg_start + i]; + if (arg.type == .skip) { + rt_arg_idx += 1; + continue; + } + const final_arg = try matchTemplateArg(c, arg, template, src_chunk, &template_ctx, i, config); + c.arg_stack.items[arg_start + i] = final_arg; + + if (final_arg.resolve_t == .rt) { + c.ir.setArrayItem(loc, u32, rt_arg_idx, final_arg.res.rt.irIdx); + rt_arg_idx += 1; + } else if (final_arg.resolve_t == .ct) { + c.valueStack.items[ct_arg_start + rt_arg_idx] = final_arg.res.ct.value; + rt_arg_idx += 1; + } else if (final_arg.resolve_t == .template) { + const name = src_chunk.ast.nodeString(template.func_params[i].name_type); + try template_ctx.setCtParam(c.alloc, name, final_arg.res.template.value); + } else if (final_arg.resolve_t == .incompat) { + return error.Unexpected; + } + } + + try sema.pushSavedResolveContext(src_chunk, template_ctx); + const ret_t = try sema.resolveReturnTypeSpecNode(src_chunk, template.func_ret); + template_ctx = sema.saveResolveContext(src_chunk); + + // Check return type. + if (!cy.types.isValidReturnType(c.compiler, ret_t, cstr.ret)) { + const ret_name = try c.sema.allocTypeName(ret_t); + defer c.alloc.free(ret_name); + return c.reportErrorFmt("Expected `void` return. Found `{}`.", &.{v(ret_name)}, node); + } + + // Build template args from context. + const targ_start = c.valueStack.items.len; + defer { + for (c.valueStack.items[targ_start..]) |arg| { + c.vm.release(arg); + } + c.valueStack.items.len = targ_start; + } + for (template.params) |tparam| { + const arg = template_ctx.ct_params.get(tparam.name).?; + c.vm.retain(arg); + try c.valueStack.append(c.alloc, arg); + } + const targs = c.valueStack.items[targ_start..]; + + // Generate function. + const func = try cte.expandFuncTemplate(c, template, targs); + + if (cstr.ct_call) { + const ct_args = c.valueStack.items[ct_arg_start..]; + const res = try cte.callFunc(c, func, ct_args); + return .{ + .func = func, + .data = .{ .ct = res }, + }; + } else { + return .{ + .func = func, + .data = .{ .rt = .{ + .args_loc = loc, + .nargs = rt_arg_idx, + }} + }; + } +} + pub fn matchFunc(c: *cy.Chunk, func: *cy.sym.Func, arg_start: usize, nargs: usize, cstr: CallCstr, node: *ast.Node) !FuncResult { const func_sig = c.sema.getFuncSig(func.funcSigId); const params = func_sig.params(); @@ -140,22 +258,16 @@ pub fn matchFunc(c: *cy.Chunk, func: *cy.sym.Func, arg_start: usize, nargs: usiz return reportIncompatCallFunc(c, func, arg_start, cstr.ret, node); } - var final_func = func; - if (func.type == .template) { - // Generate function. - const template_args = c.valueStack.items[template_arg_start..]; - final_func = try cte.expandFuncTemplate(c, func, template_args); - } if (cstr.ct_call) { const ct_args = c.valueStack.items[ct_arg_start..]; - const res = try cte.callFunc(c, final_func, ct_args); + const res = try cte.callFunc(c, func, ct_args); return .{ - .func = final_func, + .func = func, .data = .{ .ct = res }, }; } else { return .{ - .func = final_func, + .func = func, .data = .{ .rt = .{ .args_loc = loc, .nargs = rt_arg_idx, @@ -242,20 +354,13 @@ fn matchOverloadedFunc(c: *cy.Chunk, func: *cy.Func, arg_start: usize, nargs: us return null; } - var final_func = func; - if (func.type == .template) { - // Generate function. - const template_args = c.valueStack.items[template_arg_start..]; - final_func = try cte.expandFuncTemplate(c, func, template_args); - has_dyn_arg = false; - } if (cstr.ct_call) { return error.TODO; } else { return .{ // Becomes a dynamic call if this is an overloaded function with a dynamic arg. .dyn_call = has_dyn_arg, - .func = final_func, + .func = func, .data = .{ .rt = .{ .args_loc = args_loc, .nargs = rt_arg_idx, @@ -302,29 +407,34 @@ const MatchConfig = struct { ct_call: bool, }; -fn matchArg(c: *cy.Chunk, arg: Argument, param: sema.FuncParam, config: MatchConfig) !Argument { - if (param.template or config.ct_call) { +fn getFuncTemplateParamType(c: *cy.Chunk, template: *cy.sym.FuncTemplate, ctx: *sema.ResolveContext, idx: usize) !cy.TypeId { + if (idx == 0 and std.mem.eql(u8, c.ast.nodeString(template.func_params[idx].name_type), "self")) { + return template.head.parent.?.getStaticType().?; + } else { + const spec_idx = sema.indexOfTypedParam(template.func_params, idx).?; + try sema.pushSavedResolveContext(c, ctx.*); + defer ctx.* = sema.saveResolveContext(c); + return try sema.resolveTypeSpecNode(c, template.func_params[spec_idx].type); + } +} + +fn matchTemplateArg(c: *cy.Chunk, arg: Argument, template: *cy.sym.FuncTemplate, template_c: *cy.Chunk, + template_ctx: *sema.ResolveContext, idx: usize, config: MatchConfig) !Argument { + const param = template.func_params[idx]; + if (param.sema_tparam or config.ct_call) { + const param_t = try getFuncTemplateParamType(template_c, template, template_ctx, idx); // Compile-time param. const ct_value = (try cte.resolveCtValueOpt(c, arg.node)) orelse { - if (config.ct_call) { - return c.reportError("Expected compile-time argument.", arg.node); - } - var new_arg = arg; - new_arg.resolve_t = .incompat; - new_arg.res = .{ .incompat = cy.NullId }; - return new_arg; + return c.reportError("Expected compile-time argument.", arg.node); }; - if (ct_value.type != param.type) { - c.vm.release(ct_value.value); - var new_arg = arg; - new_arg.resolve_t = .incompat; - new_arg.res = .{ .incompat = ct_value.type }; - return new_arg; + if (ct_value.type != param_t) { + defer c.vm.release(ct_value.value); + return sema.reportIncompatType(c, param_t, ct_value.type, arg.node); } var new_arg = arg; - if (param.template) { - try c.valueStack.append(c.alloc, ct_value.value); + if (param.sema_tparam) { new_arg.resolve_t = .template; + new_arg.res = .{ .template = ct_value }; } else { new_arg.resolve_t = .ct; new_arg.res = .{ .ct = ct_value }; @@ -332,29 +442,24 @@ fn matchArg(c: *cy.Chunk, arg: Argument, param: sema.FuncParam, config: MatchCon return new_arg; } - var target_t = try resolveTargetParam(c, param.type, config.template_arg_start); - var res = try resolveRtArg(c, arg, arg.node, target_t, config.single_func); + var target_t: ?cy.TypeId = null; + if (!param.sema_infer_tparam) { + target_t = try getFuncTemplateParamType(template_c, template, template_ctx, idx); + } + var res = try resolveRtArg(c, arg, arg.node, target_t, true); - const type_e = c.sema.types.items[param.type]; - if (type_e.info.ct_infer) { - if (!try inferCtArgs(c, res.type.id, param.type)) { - var new_arg = arg; - new_arg.resolve_t = .incompat; - new_arg.res = .{ .incompat = res.type.id }; - return new_arg; - } - target_t = (try resolveTemplateParamType(c, param.type, config.template_arg_start, true)) orelse { - return error.Unexpected; - }; + if (param.sema_infer_tparam) { + // Infer template params. + try sema.pushSavedResolveContext(template_c, template_ctx.*); + try inferCtArgs(c, res.type.id, template, template_c, param.type.?, arg.node); + template_ctx.* = sema.saveResolveContext(template_c); + target_t = res.type.id; } const ct_compat = cy.types.isTypeSymCompat(c.compiler, res.type.id, target_t.?); const rt_compat = res.type.isDynAny(); if (!ct_compat and !rt_compat) { - var new_arg = arg; - new_arg.resolve_t = .incompat; - new_arg.res = .{ .incompat = res.type.id }; - return new_arg; + return sema.reportIncompatType(c, target_t.?, res.type.id, arg.node); } if (rt_compat and config.single_func) { @@ -370,163 +475,63 @@ fn matchArg(c: *cy.Chunk, arg: Argument, param: sema.FuncParam, config: MatchCon return resolved_arg; } -/// CtInfer types are extracted from `arg_t`. -fn inferCtArgs(c: *cy.Chunk, arg_t: cy.TypeId, infer_t: cy.TypeId) !bool { - const infer_e = c.sema.types.items[infer_t]; - if (infer_e.kind == .ct_infer) { - const ct_arg = try c.vm.allocType(arg_t); - try c.valueStack.append(c.alloc, ct_arg); - return true; - } - - // Infer nested. eg. `A[#B]`. - const type_e = c.sema.types.items[arg_t]; - const variant = type_e.sym.getVariant() orelse { - return false; - }; - - const infer_variant = infer_e.sym.getVariant().?; - if (variant.getSymTemplate() != infer_variant.getSymTemplate()) { - // Parent templates don't match. - return false; - } - - for (infer_variant.args, 0..) |arg, i| { - if (!try inferCtArgValues(c, variant.args[i], arg)) { - return false; - } - } - return true; -} - -fn inferCtArgValues(c: *cy.Chunk, arg: cy.Value, infer: cy.Value) !bool { - if (inferCtArgValueEq(c, arg, infer)) { - return true; - } - - if (infer.getTypeId() != bt.Type) { - return false; - } - - const infer_t = infer.asHeapObject().type.type; - const infer_e = c.sema.types.items[infer_t]; - if (infer_e.kind == .ct_infer) { - c.vm.retain(arg); - try c.valueStack.append(c.alloc, arg); - return true; - } - - if (!infer_e.info.ct_infer) { - return false; - } - - if (arg.getTypeId() != bt.Type) { - return false; - } - - const type_e = c.sema.types.items[arg.asHeapObject().type.type]; - const variant = type_e.sym.getVariant() orelse { - return false; - }; - - const infer_variant = infer_e.sym.getVariant().?; - if (variant.getSymTemplate() != infer_variant.getSymTemplate()) { - // Parent templates don't match. - return false; - } - - for (infer_variant.args, 0..) |infer_arg, i| { - if (!try inferCtArgValues(c, variant.args[i], infer_arg)) { - return false; - } - } - return true; -} - -fn inferCtArgValueEq(c: *cy.Chunk, arg: cy.Value, infer: cy.Value) bool { - _ = c; - if (arg.getTypeId() != infer.getTypeId()) { - return false; - } - - switch (infer.getTypeId()) { - bt.Type => { - return infer.asHeapObject().type.type == arg.asHeapObject().type.type; - }, - else => { - // TODO: Support more arg types. - return false; +fn matchArg(c: *cy.Chunk, arg: Argument, param: sema.FuncParam, config: MatchConfig) !Argument { + if (config.ct_call) { + // Compile-time param. + const ct_value = (try cte.resolveCtValueOpt(c, arg.node)) orelse { + if (config.ct_call) { + return c.reportError("Expected compile-time argument.", arg.node); + } + var new_arg = arg; + new_arg.resolve_t = .incompat; + new_arg.res = .{ .incompat = .{ + .res_t = cy.NullId, + .target_t = param.type, + }}; + return new_arg; + }; + if (ct_value.type != param.type) { + c.vm.release(ct_value.value); + var new_arg = arg; + new_arg.resolve_t = .incompat; + new_arg.res = .{ .incompat = .{ + .res_t = ct_value.type, + .target_t = param.type, + }}; + return new_arg; } + var new_arg = arg; + new_arg.resolve_t = .ct; + new_arg.res = .{ .ct = ct_value }; + return new_arg; } -} -/// Returns the target param type at index. Returns null if type should be deduced from arg. -/// resolveSymType isn't used because that performs resolving on a node rather than a type. -fn resolveTargetParam(c: *cy.Chunk, param_t: cy.TypeId, ct_arg_start: usize) !?cy.TypeId { - const type_e = c.sema.types.items[param_t]; - if (type_e.info.ct_ref or type_e.info.ct_infer) { - return try resolveTemplateParamType(c, param_t, ct_arg_start, false); - } else { - return param_t; - } -} - -fn resolveTemplateParamType(c: *cy.Chunk, param_t: cy.TypeId, ct_arg_start: usize, resolve_infer: bool) !?cy.TypeId { - const type_e = c.sema.types.items[param_t]; - if (type_e.info.ct_infer and !resolve_infer) { - return null; - } - return try resolveTemplateParamType2(c, param_t, ct_arg_start); -} + const target_t = param.type; + var res = try resolveRtArg(c, arg, arg.node, target_t, config.single_func); -fn resolveTemplateParamType2(c: *cy.Chunk, type_id: cy.TypeId, ct_arg_start: usize) !cy.TypeId { - const type_e = c.sema.types.items[type_id]; - if (type_e.kind == .ct_infer) { - const ct_arg = c.valueStack.items[ct_arg_start + type_e.data.ct_infer.ct_param_idx]; - if (ct_arg.getTypeId() != bt.Type) { - return error.TODO; - } - return ct_arg.asHeapObject().type.type; + const ct_compat = cy.types.isTypeSymCompat(c.compiler, res.type.id, target_t); + const rt_compat = res.type.isDynAny(); + if (!ct_compat and !rt_compat) { + var new_arg = arg; + new_arg.resolve_t = .incompat; + new_arg.res = .{ .incompat = .{ + .res_t = res.type.id, + .target_t = param.type, + }}; + return new_arg; } - if (type_e.kind == .ct_ref) { - const ct_arg = c.valueStack.items[ct_arg_start + type_e.data.ct_ref.ct_param_idx]; - if (ct_arg.getTypeId() != bt.Type) { - return error.TODO; - } - return ct_arg.asHeapObject().type.type; + if (rt_compat and config.single_func) { + // Insert rt arg type check. + const loc = try c.unboxOrCheck(target_t, res, arg.node); + res.irIdx = loc; + res.type = cy.types.CompactType.init(target_t); } - // Contains nested ct_infer, ct_refs. - if (type_e.sym.getVariant()) |variant| { - const template = variant.getSymTemplate(); - - const value_start = c.valueStack.items.len; - defer { - const values = c.valueStack.items[value_start..]; - for (values) |val| { - c.vm.release(val); - } - c.valueStack.items.len = value_start; - } - - for (variant.args) |arg| { - if (arg.getTypeId() != bt.Type) { - return error.TODO; - } - - const arg_t = arg.asHeapObject().type.type; - const rarg_t = try resolveTemplateParamType2(c, arg_t, ct_arg_start); - const rarg_t_v = try c.vm.allocType(rarg_t); - try c.valueStack.append(c.alloc, rarg_t_v); - } - - const args = c.valueStack.items[value_start..]; - const type_sym = try cte.expandTemplate(c, template, args); - return type_sym.getStaticType().?; - } else { - return error.Unexpected; - } + var resolved_arg = arg; + resolved_arg.res = .{ .rt = res }; + resolved_arg.resolve_t = .rt; + return resolved_arg; } fn reportIncompatCallFuncSym(c: *cy.Chunk, sym: *cy.sym.FuncSym, arg_start: usize, ret_cstr: cy.types.ReturnCstr, node: *ast.Node) anyerror { @@ -542,7 +547,7 @@ fn reportIncompatCallFuncSym(c: *cy.Chunk, sym: *cy.sym.FuncSym, arg_start: usiz } else if (arg.resolve_t == .ct) { try c.typeStack.append(c.alloc, arg.res.ct.type); } else { - try c.typeStack.append(c.alloc, arg.res.incompat); + try c.typeStack.append(c.alloc, arg.res.incompat.res_t); } } const types = c.typeStack.items[type_start..]; @@ -562,9 +567,127 @@ fn reportIncompatCallFunc(c: *cy.Chunk, func: *cy.Func, arg_start: usize, ret_cs } else if (arg.resolve_t == .ct) { try c.typeStack.append(c.alloc, arg.res.ct.type); } else { - try c.typeStack.append(c.alloc, arg.res.incompat); + try c.typeStack.append(c.alloc, arg.res.incompat.res_t); } } const types = c.typeStack.items[type_start..]; return sema.reportIncompatCallFunc(c, func, types, ret_cstr, node); +} + +fn expectTypeFromTemplate(c: *cy.Chunk, type_id: cy.TypeId, exp: *cy.sym.Template, node: *ast.Node) !*cy.sym.Variant { + const variant = c.sema.getTypeSym(type_id).getVariant() orelse { + const name = try c.sema.allocTypeName(type_id); + defer c.alloc.free(name); + return c.reportErrorFmt("Expected `{}` to be an expanded type.", &.{v(name)}, node); + }; + + if (variant.type != .sym or variant.data.sym.template != exp) { + return c.reportErrorFmt("Expected template `{}`. Found `{}`.", &.{v(exp.head.name()), v(variant.data.sym.template.head.name())}, node); + } + return variant; +} + +/// CtInfer types are extracted from `arg_t`. +fn inferCtArgs(c: *cy.Chunk, arg_t: cy.TypeId, template: *cy.sym.FuncTemplate, template_c: *cy.Chunk, template_n: *ast.Node, node: *ast.Node) !void { + switch (template_n.type()) { + .ident => { + const name = template_c.ast.nodeString(template_n); + if (template.indexOfParam(name) != null) { + const ctx = sema.getResolveContext(template_c); + if (!ctx.ct_params.contains(name)) { + const targ = try c.vm.allocType(arg_t); + try ctx.setCtParam(c.alloc, name, targ); + return; + } + } + const act_t = try cte.resolveCtType(template_c, template_n); + if (arg_t != arg_t) { + return sema.reportIncompatType(template_c, arg_t, act_t, template_n); + } + }, + .ptr => { + const ptr = template_n.cast(.ptr); + const variant = try expectTypeFromTemplate(c, arg_t, c.sema.pointer_tmpl, node); + try inferCtArgValue(c, variant.args[0], template, template_c, ptr.elem); + }, + .ptr_slice => { + const ptr_slice = template_n.cast(.ptr_slice); + const variant = try expectTypeFromTemplate(c, arg_t, c.sema.ptr_slice_tmpl, node); + try inferCtArgValue(c, variant.args[0], template, template_c, ptr_slice.elem); + }, + .ref_slice => { + const ref_slice = template_n.cast(.ref_slice); + const variant = try expectTypeFromTemplate(c, arg_t, c.sema.ref_slice_tmpl, node); + try inferCtArgValue(c, variant.args[0], template, template_c, ref_slice.elem); + }, + .array_expr => { + // Infer nested. (e.g. `A[T]`) + const array_expr = template_n.cast(.array_expr); + const variant = c.sema.getTypeSym(arg_t).getVariant() orelse { + const name = try c.sema.allocTypeName(arg_t); + defer c.alloc.free(name); + return c.reportErrorFmt("Expected `{}` to be an expanded type.", &.{v(name)}, node); + }; + + const template_t = try cte.resolveCtSym(template_c, array_expr.left); + if (template_t.type != .template) { + return template_c.reportErrorFmt("Expected template. Found `{}`.", &.{v(template_t.type)}, array_expr.left); + } + + if (variant.type != .sym or variant.data.sym.template != template_t.cast(.template)) { + return c.reportErrorFmt("Expected template `{}`. Found `{}`.", &.{v(template_t.name()), v(variant.data.sym.template.head.name())}, node); + } + + if (variant.args.len != array_expr.args.len) { + return template_c.reportErrorFmt("Expected `{}` args. Found `{}`.", &.{v(variant.args.len), v(array_expr.args.len)}, template_n); + } + for (variant.args, 0..) |targ, i| { + try inferCtArgValue(c, targ, template, template_c, array_expr.args[i]); + } + }, + else => { + return template_c.reportErrorFmt("Unsupported infer node `{}`.", &.{v(template_n.type())}, template_n); + } + } +} + +fn inferCtArgValue(c: *cy.Chunk, arg: cy.Value, template: *cy.sym.FuncTemplate, template_c: *cy.Chunk, template_n: *ast.Node) !void { + switch (template_n.type()) { + .ident => { + const name = template_c.ast.nodeString(template_n); + if (template.indexOfParam(name) != null) { + const ctx = sema.getResolveContext(template_c); + if (!ctx.ct_params.contains(name)) { + c.vm.retain(arg); + try ctx.setCtParam(c.alloc, name, arg); + return; + } + } + const act_v = try cte.resolveCtValue(template_c, template_n); + defer c.vm.release(act_v.value); + try assertValueEq(template_c, arg, act_v.value, template_n); + }, + else => { + return template_c.reportErrorFmt("Unsupported infer node `{}`.", &.{v(template_n.type())}, template_n); + } + } +} + +fn assertValueEq(c: *cy.Chunk, exp: cy.Value, act: cy.Value, node: *ast.Node) !void { + if (exp.getTypeId() != act.getTypeId()) { + return sema.reportIncompatType(c, exp.getTypeId(), act.getTypeId(), node); + } + + switch (exp.getTypeId()) { + bt.Type => { + if (exp.asHeapObject().type.type != act.asHeapObject().type.type) { + return sema.reportIncompatType(c, exp.asHeapObject().type.type, act.asHeapObject().type.type, node); + } + }, + else => { + const type_name = try c.sema.allocTypeName(exp.getTypeId()); + defer c.alloc.free(type_name); + return c.reportErrorFmt("Unsupport value type: `{s}`", &.{v(type_name)}, node); + } + } } \ No newline at end of file diff --git a/src/std/os_ffi.zig b/src/std/os_ffi.zig index f0eb04603..76e056e72 100644 --- a/src/std/os_ffi.zig +++ b/src/std/os_ffi.zig @@ -888,12 +888,12 @@ pub fn ffiBindLib(vm: *cy.VM, config: BindLibConfig) !Value { tempTypes.len = 0; if (!config.gen_table) { // Self param. - const param_t = sema.FuncParam.initRt(bt.Any); + const param_t = sema.FuncParam.init(bt.Any); try tempTypes.append(param_t); } for (cfunc.params) |param| { const typeId = try toCyType(vm, param); - const param_t = sema.FuncParam.initRt(typeId); + const param_t = sema.FuncParam.init(typeId); try tempTypes.append(param_t); } diff --git a/src/std/test.cy b/src/std/test.cy index 6ae8c4124..05949db23 100644 --- a/src/std/test.cy +++ b/src/std/test.cy @@ -3,14 +3,14 @@ --| Returns whether two values are equal. --| Panics with `error.AssertError` if types or values do not match up. -func eq(a #T, b T) bool: +func eq[T](a T, b T) bool: return eq_(typeid[T], a, b) -@host -func eq_(t int, a #T, b T) bool +@host -func eq_[T](t int, a T, b T) bool --| Returns `true` if two lists have the same size and the elements are equal --| as if `eq` was called on those corresponding elements. -func eqList(a List[#T], b List[T]) bool: +func eqList[T](a List[T], b List[T]) bool: if a.len() != b.len(): eprint("Length mismatch: $(a.len()) != $(b.len())") return false @@ -21,7 +21,7 @@ func eqList(a List[#T], b List[T]) bool: --| Returns `true` if two slices have the same size and the elements are equal --| as if `eq` was called on those corresponding elements. -func eqSlice(a []#T, b []T) bool: +func eqSlice[T](a []T, b []T) bool: if a.len() != b.len(): eprint("Length mismatch: $(a.len()) != $(b.len())") return false @@ -31,7 +31,7 @@ func eqSlice(a []#T, b []T) bool: return true --| Returns `true` if two numbers are near each other within epsilon 1e-5. -@host func eqNear(a #T, b T) bool +@host func eqNear[T](a T, b T) bool func fail(): throw error.AssertError diff --git a/src/sym.zig b/src/sym.zig index 5829458da..47e234e4d 100644 --- a/src/sym.zig +++ b/src/sym.zig @@ -32,6 +32,7 @@ pub const SymType = enum(u8) { /// Unresolved distinct type. distinct_t, + func_template, template, field, @@ -91,6 +92,18 @@ pub const Sym = extern struct { deinitVariantValues(vm, variant); } }, + .func_template => { + const template = self.cast(.func_template); + var iter = template.variant_cache.iterator(); + while (iter.next()) |e| { + const variant = e.value_ptr.*; + for (variant.args) |arg| { + vm.release(arg); + } + vm.alloc.free(variant.args); + variant.args = &.{}; + } + }, else => {}, } } @@ -134,6 +147,11 @@ pub const Sym = extern struct { alloc.free(enumType.members[0..enumType.numMembers]); alloc.destroy(enumType); }, + .func_template => { + const template = self.cast(.func_template); + deinitFuncTemplate(vm, template); + alloc.destroy(template); + }, .template => { const template = self.cast(.template); deinitTemplate(vm, template); @@ -249,6 +267,7 @@ pub const Sym = extern struct { .hostVar, .module_alias, .func, + .func_template, .template, .distinct_t, .field, @@ -290,6 +309,7 @@ pub const Sym = extern struct { .distinct_t => return @ptrCast(&self.cast(.distinct_t).mod), .typeAlias => return @ptrCast(&self.cast(.typeAlias).mod), .template => return @ptrCast(&self.cast(.template).mod), + .func_template => return @ptrCast(&self.cast(.func_template).mod), .trait_t => return @ptrCast(&self.cast(.trait_t).mod), .use_alias, .module_alias, @@ -338,6 +358,7 @@ pub const Sym = extern struct { .placeholder, .null, .field, + .func_template, .template, .enumMember, .func, @@ -371,6 +392,7 @@ pub const Sym = extern struct { .hostobj_t, .null, .field, + .func_template, .template, .enumMember, .func, @@ -444,6 +466,7 @@ fn SymChild(comptime symT: SymType) type { .typeAlias => TypeAlias, .distinct_t => DistinctType, .template => Template, + .func_template => FuncTemplate, .field => Field, .use_alias => UseAlias, .module_alias => ModuleAlias, @@ -575,6 +598,70 @@ pub const ValueType = extern struct { head: Sym, }; +pub const FuncTemplateParam = struct { + name: []const u8, + // Func parameter index where it is declared. + decl_idx: u32, + infer: bool, +}; + +pub const FuncTemplate = struct { + head: Sym, + decl: *ast.TemplateDecl, + func_params: []const *ast.FuncParam, + func_ret: ?*ast.Node, + params: []FuncTemplateParam, + resolved: bool, + + /// Template args to variant. Keys are not owned. + variant_cache: std.HashMapUnmanaged([]const cy.Value, *Variant, VariantKeyContext, 80), + variants: std.ArrayListUnmanaged(*Variant), + + mod: vmc.Module, + + pub fn deinit(self: *FuncTemplate, alloc: std.mem.Allocator) void { + for (self.variants.items) |variant| { + alloc.destroy(variant); + } + self.variants.deinit(alloc); + self.variant_cache.deinit(alloc); + + alloc.free(self.params); + } + + pub fn isResolved(self: *FuncTemplate) bool { + return self.resolved; + } + + pub fn getMod(self: *FuncTemplate) *cy.Module { + return @ptrCast(&self.mod); + } + + pub fn indexOfParam(self: *FuncTemplate, name: []const u8) ?usize { + for (self.params, 0..) |param, i| { + if (std.mem.eql(u8, param.name, name)) { + return i; + } + } + return null; + } + + pub fn chunk(self: *const FuncTemplate) *cy.Chunk { + return self.head.parent.?.getMod().?.chunk; + } +}; + +fn deinitFuncTemplate(vm: *cy.VM, template: *FuncTemplate) void { + template.getMod().deinit(vm.alloc); + + for (template.variants.items) |variant| { + vm.alloc.destroy(variant); + } + template.variants.deinit(vm.alloc); + template.variant_cache.deinit(vm.alloc); + vm.alloc.free(template.params); +} + /// This is similar to SymType but has custom_t. pub const TemplateType = enum { object_t, @@ -582,7 +669,7 @@ pub const TemplateType = enum { enum_t, distinct_t, custom_t, - ct_func, + value, }; /// TODO: Consider splitting into Template and FuncTemplate. @@ -1016,35 +1103,11 @@ pub const Chunk = extern struct { } }; -pub const FuncTemplate = struct { - sig: cy.sema.FuncSigId, - - params: []const FuncTemplateParam, - - /// Template args to variant. Keys are not owned. - variant_cache: std.HashMapUnmanaged([]const cy.Value, *Variant, VariantKeyContext, 80), - variants: std.ArrayListUnmanaged(*Variant), - - pub fn deinit(self: *FuncTemplate, alloc: std.mem.Allocator) void { - for (self.variants.items) |variant| { - alloc.destroy(variant); - } - self.variants.deinit(alloc); - self.variant_cache.deinit(alloc); - alloc.free(self.params); - } -}; - -pub const FuncTemplateParam = struct { - name: []const u8, -}; - pub const FuncKind = enum { hostFunc, userFunc, userLambda, trait, - template, }; pub const Func = struct { @@ -1069,7 +1132,6 @@ pub const Func = struct { trait: struct { vtable_idx: u32, }, - template: *FuncTemplate, userFunc: struct { /// Currently used to invalidate the IR func block when removing temporary functions. /// See `cte.expandValueTemplate`. @@ -1093,33 +1155,6 @@ pub const Func = struct { /// This is useful for comptime evaluation to check whether it needs to compile deps. emitted_deps: bool = false, - pub fn deinit(self: *Func, alloc: std.mem.Allocator) void { - if (self.type == .template and self.isResolved()) { - self.data.template.deinit(alloc); - alloc.destroy(self.data.template); - } - } - - pub fn deinitValues(self: *Func, vm: *cy.VM) void { - if (self.type != .template) { - return; - } - - if (self.isResolved()) { - const template = self.data.template; - - var iter = template.variant_cache.iterator(); - while (iter.next()) |e| { - const variant = e.value_ptr.*; - for (variant.args) |arg| { - vm.release(arg); - } - vm.alloc.free(variant.args); - variant.args = &.{}; - } - } - } - pub fn isResolved(self: Func) bool { return self.funcSigId != cy.NullId; } @@ -1184,6 +1219,7 @@ pub const ChunkExt = struct { .null, .module_alias, .distinct_t, + .func_template, .template, .chunk => return null, } @@ -1442,6 +1478,24 @@ pub const ChunkExt = struct { return sym; } + pub fn createFuncTemplate(c: *cy.Chunk, parent: *Sym, name: []const u8, tparams: []FuncTemplateParam, decl: *ast.TemplateDecl) !*FuncTemplate { + const func_decl = decl.child_decl.cast(.funcDecl); + const sym = try createSym(c.alloc, .func_template, .{ + .head = Sym.init(.func_template, parent, name), + .func_params = func_decl.params, + .func_ret = func_decl.ret, + .params = tparams, + .resolved = false, + .decl = decl, + .variant_cache = .{}, + .variants = .{}, + .mod = undefined, + }); + sym.getMod().* = cy.Module.init(c); + try c.syms.append(c.alloc, @ptrCast(sym)); + return sym; + } + pub fn createTemplate(c: *cy.Chunk, parent: *Sym, name: []const u8, kind: TemplateType, decl: *ast.TemplateDecl) !*Template { const sym = try createSym(c.alloc, .template, .{ diff --git a/src/tokenizer.zig b/src/tokenizer.zig index e46dadb49..2fbf21414 100644 --- a/src/tokenizer.zig +++ b/src/tokenizer.zig @@ -17,6 +17,7 @@ const keywords = std.ComptimeStringMap(TokenType, .{ .{ "coresume", .coresume_k }, .{ "coyield", .coyield_k }, .{ "cstruct", .cstruct_k }, + .{ "def", .def_k }, .{ "dyn", .dyn_k }, .{ "else", .else_k }, .{ "enum", .enum_k }, @@ -72,6 +73,7 @@ pub const TokenType = enum(u8) { coyield_k, cstruct_k, dec, + def_k, dot, dot_bang, dot_dot, @@ -1034,6 +1036,6 @@ test "tokenizer internals." { try tt.eq(@alignOf(Token), 4); try tt.eq(@sizeOf(TokenizeState), 4); - try tt.eq(std.enums.values(TokenType).len, 95); - try tt.eq(keywords.kvs.len, 41); + try tt.eq(std.enums.values(TokenType).len, 97); + try tt.eq(keywords.kvs.len, 42); } \ No newline at end of file diff --git a/src/types.zig b/src/types.zig index 7db5567d6..65e4741d8 100644 --- a/src/types.zig +++ b/src/types.zig @@ -27,7 +27,6 @@ pub const TypeKind = enum(u8) { trait, bare, ct_ref, - ct_infer, distinct, array, func_ptr, @@ -41,15 +40,12 @@ pub const TypeInfo = packed struct { /// If `true`, invoke finalizer before releasing children. custom_pre: bool = false, - /// Whether this type or a child parameter contains a ct_infer. - ct_infer: bool = false, - /// Whether this type or a child parameter contains a ct_ref. ct_ref: bool = false, load_all_methods: bool = false, - padding: u3 = undefined, + padding: u4 = undefined, }; pub const Type = extern struct { @@ -94,9 +90,6 @@ pub const Type = extern struct { ct_ref: extern struct { ct_param_idx: u32, }, - ct_infer: extern struct { - ct_param_idx: u32, - }, array: extern struct { n: usize, elem_t: cy.TypeId, diff --git a/src/vm.h b/src/vm.h index 1e7354a25..bbddce5ab 100644 --- a/src/vm.h +++ b/src/vm.h @@ -708,7 +708,7 @@ typedef struct ZList { #define TYPE_KIND_STRUCT 8 #define TYPE_KIND_OPTION 9 -#define TYPE_KIND_FUNC_UNION 17 +#define TYPE_KIND_FUNC_UNION 16 typedef struct TypeEntry { void* sym; diff --git a/src/vm.zig b/src/vm.zig index 8561b0d2e..feb6e25f5 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -2348,7 +2348,7 @@ fn panicIncompatibleLambdaSig(vm: *cy.VM, args: []const Value, cstrFuncSigId: se defer vm.alloc.free(argsSigStr); return vm.panicFmt( - \\Incompatible call arguments `{}` + \\Incompatible call arguments `({})` \\to the lambda `func {}`. , &.{ v(argsSigStr), v(cstrFuncSigStr), @@ -2375,7 +2375,7 @@ fn panicIncompatibleCallSymSig(vm: *cy.VM, overload_entry: u32, args: []const Va var msg: std.ArrayListUnmanaged(u8) = .{}; defer msg.deinit(vm.alloc); const w = msg.writer(vm.alloc); - try w.print("Can not find compatible function for call: `{s}{s}`.", .{name, call_args_str}); + try w.print("Can not find compatible function for call: `{s}({s})`.", .{name, call_args_str}); // if (num_ret == 1) { // try w.writeAll(" Expects non-void return."); // } @@ -2423,7 +2423,7 @@ fn panicIncompatibleMethodSig( const call_args_str = vm.compiler.sema.allocTypesStr(typeIds[1..], null) catch return error.OutOfMemory; defer vm.alloc.free(call_args_str); - try w.print("Can not find compatible method for call: `({s}) {s}{s}`.", .{typeName, name, call_args_str}); + try w.print("Can not find compatible method for call: `({s}) {s}({s})`.", .{typeName, name, call_args_str}); try w.writeAll("\n"); try w.print("Methods named `{s}`:\n", .{name}); diff --git a/test/functions/template_functions.cy b/test/functions/template_functions.cy index ec8aaad4b..a59939772 100644 --- a/test/functions/template_functions.cy +++ b/test/functions/template_functions.cy @@ -1,7 +1,7 @@ use test -- Inferred template param. -func add(a #T, b T) T: +func add[T](a T, b T) T: return a + b test.eq(add(1, 2), 3) test.eq(add(1.0, 2.0), 3.0) @@ -11,7 +11,7 @@ test.eq(add[int](1, 2), 3) test.eq(add[float](1, 2), 3.0) -- Explicit template param. -func add2(#T type, a T, b T) T: +func add2[T](T type, a T, b T) T: return a + b test.eq(add2(int, 1, 2), 3) test.eq(add2(float, 1.0, 2.0), 3.0) @@ -21,12 +21,12 @@ test.eq(add2[int](1, 2), 3) test.eq(add2[float](1, 2), 3.0) -- Infer param declared after runtime param. -func add3(i int, a #T, b T) T: +func add3[T](i int, a T, b T) T: return i + a + b test.eq(add3(1, 2, 3), 6) -- All compile-time parameters still generates different runtime functions. -func foo(#T type) int: +func foo[T](T type) int: return 123 test.eq(foo[int], foo[int]) test.assert(foo[int] != foo[float]) diff --git a/test/functions/template_value.cy b/test/functions/template_value.cy index b01f5fdfc..aadaa645d 100644 --- a/test/functions/template_value.cy +++ b/test/functions/template_value.cy @@ -1,6 +1,6 @@ use test -func GetType[ID String] type: +def GetType[ID String] type: if ID == 'bool': return bool else ID == 'int': @@ -23,7 +23,7 @@ test.eq(c, 'xyz') type GenFoo[T type]: a T -func Foo[T type] type: +def Foo[T type] type: if T == int: return int else: diff --git a/test/functions/template_value_host_error.cy b/test/functions/template_value_host_error.cy index ee2347061..081ca62eb 100644 --- a/test/functions/template_value_host_error.cy +++ b/test/functions/template_value_host_error.cy @@ -1,9 +1,10 @@ -@host func GetType[ID String] type +@host def GetType[ID String] type: + return int --cytest: error --CompileError: A value template can not be binded to a `@host` function. Consider invoking a `@host` function in the template body instead. -- --main:1:7: ---@host func GetType[ID String] type +--@host def GetType[ID String] type: -- ^ -- \ No newline at end of file diff --git a/test/functions/template_value_throw_error.cy b/test/functions/template_value_throw_error.cy index ee9fb2c05..aaa296ce0 100644 --- a/test/functions/template_value_throw_error.cy +++ b/test/functions/template_value_throw_error.cy @@ -1,6 +1,6 @@ use test -func GetType[ID String] type: +def GetType[ID String] type: if ID == 'bool': return bool else ID == 'int': diff --git a/test/memory/default_memory.cy b/test/memory/default_memory.cy index 6970a3120..455cac064 100644 --- a/test/memory/default_memory.cy +++ b/test/memory/default_memory.cy @@ -30,7 +30,7 @@ ints[0] = 123 ints[1] = 234 test.eq(ints[0], 123) test.eq(ints[1], 234) -mem.free(ints) +mem.free_(ints) -- Memory.alloc cstruct var foos = mem.alloc(Foo, 2) @@ -45,6 +45,6 @@ foos[0].a = 234 foos[0].b = 2.34 test.eq(foos[0].a, 234) test.eq(foos[0].b, 2.34) -mem.free(foos) +mem.free_(foos) --cytest: pass \ No newline at end of file diff --git a/test/types/template_dep_param_type_error.cy b/test/types/template_dep_param_type_error.cy index 9fb847875..7ba089699 100644 --- a/test/types/template_dep_param_type_error.cy +++ b/test/types/template_dep_param_type_error.cy @@ -4,7 +4,7 @@ type Foo[T type, Value T] struct: var f = Foo[int, 10.0]{a=123} --cytest: error ---CompileError: Expected type `int`, got `float`. +--CompileError: Expected type `int`. Found `float`. -- --main:4:18: --var f = Foo[int, 10.0]{a=123}