From b87a37c9a6b9a19d1086f93173a09c078b023f2b Mon Sep 17 00:00:00 2001 From: fubark Date: Wed, 5 Jun 2024 22:22:58 -0400 Subject: [PATCH] Redesign func templates to allow template args. --- docs/docs.md | 71 +- src/ast.zig | 1 + src/bc_gen.zig | 5 + src/builtins/builtins.zig | 4 +- src/builtins/builtins_vm.cy | 18 +- src/capi.zig | 3 +- src/cgen.zig | 4 +- src/chunk.zig | 21 +- src/compiler.zig | 49 +- src/cte.zig | 131 +- src/include/cyber.h | 1 + src/lib.zig | 12 +- src/module.zig | 46 +- src/parser.zig | 99 +- src/sema.zig | 1205 ++++++++++++----- src/std/os_ffi.zig | 10 +- src/std/test.cy | 4 +- src/sym.zig | 137 +- src/types.zig | 27 +- src/vm.h | 5 +- src/vm.zig | 26 +- test/behavior_test.zig | 2 +- test/concurrency/await.cy | 4 +- test/core/lists.cy | 18 +- test/functions/call_closure_param_panic.cy | 2 +- test/functions/call_host_param_panic.cy | 2 +- .../call_static_lambda_incompat_arg_panic.cy | 2 +- test/functions/lambda_incompat_arg_panic.cy | 2 +- test/functions/template_functions.cy | 17 +- test/modules/core.cy | 4 +- test/types/objects.cy | 33 +- 31 files changed, 1383 insertions(+), 582 deletions(-) diff --git a/docs/docs.md b/docs/docs.md index 3034c7ef0..13fad7721 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -312,7 +312,7 @@ There are `29` general keywords. This list categorizes them: - [Operators](#operators): `or` `and` `not` - [Variables](#variables): [`var`](#local-variables) [`context`](#context-variables) - [Functions](#functions): `func` `return` -- [Coroutines](#fibers): `coinit` `coyield` `coresume` +- [Fibers](#fibers): `coinit` `coyield` `coresume` - [Async](#async): `await` - [Types](#custom-types): `type` `as` - [Type embedding](#type-embedding): `use` @@ -1004,7 +1004,7 @@ The dynamic type defers type checking to runtime. However, it also tracks its ow * [Distinct types.](#distinct-types) * [Traits.](#traits) * [Union types.](#union-types) -* [Generic types.](#generic-types) +* [Type templates.](#type-templates) * [Expand type template.](#expand-type-template) @@ -1311,7 +1311,7 @@ print s.!line --> 20 ## Optionals. Optionals provide **Null Safety** by forcing values to be unwrapped before they can be used. -The generic `Option` type is a choice type that either holds a `none` value or contains `some` value. The option template is defined as: +The `Option` template type is a choice type that either holds a `none` value or contains `some` value. The option template is defined as: ```cy type Option[T type] enum: case none @@ -1477,8 +1477,8 @@ print s.area() --> 20 ## Union types. > _Planned Feature_ -## Generic types. -Templates are used to specialize type declarations: +## Type templates. +Type declarations can include template parameters to create a type template: ```cy type MyContainer[T type]: id int @@ -1489,7 +1489,7 @@ type MyContainer[T type]: ``` ### Expand type template. -When the template is invoked with compile-time argument(s), a specialized version of the type is generated. +When the type template is invoked with template argument(s), a special version of the type is generated. In this example, `String` can be used as an argument since it satisfies the `type` parameter constraint: ```cy @@ -1725,9 +1725,10 @@ The `try catch` statement, `try else` and `try` expressions provide a way to cat * [Function calls.](#function-calls) * [Shorthand syntax.](#shorthand-syntax) * [Call block syntax.](#call-block-syntax) -* [Generic functions.](#generic-functions) - * [Expand function template.](#expand-function-template) - * [Infer template function.](#infer-template-function) +* [Function templates.](#function-templates) + * [Explicit template call.](#explicit-template-call) + * [Expand function.](#expand-function) + * [Infer param type.](#infer-param-type) [^top](#table-of-contents) @@ -1902,27 +1903,45 @@ Button('Count'): ``` Arguments assigned in the call block can be unordered. -## Generic functions. -Templates are used to specialize function declarations: +## 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 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. -### Expand function template. -When the template is invoked with compile-time argument(s), a specialized version of the function is generated: +### Explicit template call. +When the function is invoked with template argument(s), a special version of the function is generated and used: ```cy -print add[int](1, 2) --> 3 -print add[float](1, 2) --> 3.0 +print add(int, 1, 2) --> 3 +print add(float, 1, 2) --> 3.0 ``` -Note that invoking the template again with the same argument(s) returns the same generated function. In other words, the generated function is always memoized from the input parameters. +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. -### Infer template function. -When invoking template functions, it is possible to infer the template parameters from the call arguments. -In the following example, `add[int]` and `add[float]` are inferred from the function calls: +### 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: ```cy -print add(1, 2) --> 3 -print add(1.0, 2.0) --> 3.0 +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: +```cy +func add(a #T, b T) T: + return a + b + +print add(1, 2) --> 3 +print add(1.0, 2.0) --> 3.0 +``` +In the above example, `add[int]` and `add[float]` were inferred from the function calls: + +Nested compile-time parameters can also be inferred: +```cy +func set(m Map[#K, #V], key K, val V): + m.set(key, val) ``` # Modules. @@ -3059,7 +3078,7 @@ print str.trim(.left, ' ') * [Reflection.](#reflection) * [Attributes.](#attributes) -* [Generics.](#generics) +* [Templates.](#templates) * [Template specialization.](#template-specialization) * [Macros.](#macros) * [Compile-time execution.](#compile-time-execution) @@ -3226,10 +3245,10 @@ Attributes start with `@`. They are used as declaration modifiers. > >Bind a function, variable, or type to the host. See [libcyber](#libcyber). -## Generics. -Generics enables parametric polymorphism for types and functions. Compile-time arguments are passed to templates to generate specialized code. This facilitates developing container types and algorithms that operate on different types. +## Templates. +Templates enables parametric polymorphism for types and functions. Template arguments are passed to templates to generate specialized code. This facilitates developing container types and algorithms that operate on different types. -See [Custom Types / Generic types](#generic-types) and [Functions / Generic functions](#generic-functions). +See [Custom Types / Type templates](#type-templates) and [Functions / Function templates](#function-templates). ### Template specialization. > _Planned Feature_ diff --git a/src/ast.zig b/src/ast.zig index ee04f959d..925893009 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -354,6 +354,7 @@ pub const FuncParam = struct { name_pos: u32 align(8), name_len: u32, typeSpec: ?*Node, + ct_param: bool, }; pub const UseAlias = struct { diff --git a/src/bc_gen.zig b/src/bc_gen.zig index 586def062..1df5ffacb 100644 --- a/src/bc_gen.zig +++ b/src/bc_gen.zig @@ -40,6 +40,10 @@ 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) { + continue; + } + switch (sym.type) { .object_t => { const obj = sym.cast(.object_t); @@ -55,6 +59,7 @@ pub fn genAll(c: *cy.Compiler) !void { try c.vm.addFieldSym(@intCast(typeId), fieldSymId, @intCast(i), field.type); } }, + .dummy_t, .trait_t, .int_t, .float_t, diff --git a/src/builtins/builtins.zig b/src/builtins/builtins.zig index 3594a4942..7b8fa2ef9 100644 --- a/src/builtins/builtins.zig +++ b/src/builtins/builtins.zig @@ -227,9 +227,9 @@ const vm_types = [_]C.HostTypeEntry{ htype("any", C.CORE_TYPE(bt.Any)), htype("type", C.CORE_TYPE(bt.Type)), htype("List", C.CUSTOM_TYPE(null, listGetChildren, listFinalizer)), - htype("ListDyn", C.CORE_TYPE_EXT(bt.ListDyn, listGetChildren, listFinalizer)), + htype("ListDyn", C.CORE_TYPE_EXT(bt.ListDyn, listGetChildren, listFinalizer, true)), htype("ListIterator", C.CUSTOM_TYPE(null, listIterGetChildren, null)), - htype("ListIterDyn", C.CORE_TYPE_EXT(bt.ListIterDyn, listIterGetChildren, null)), + htype("ListIterDyn", C.CORE_TYPE_EXT(bt.ListIterDyn, listIterGetChildren, null, true)), htype("Tuple", C.CORE_TYPE(bt.Tuple)), htype("Table", C.CORE_TYPE_DECL(bt.Table)), htype("Map", C.CORE_TYPE(bt.Map)), diff --git a/src/builtins/builtins_vm.cy b/src/builtins/builtins_vm.cy index 798c19374..804cdd5cd 100644 --- a/src/builtins/builtins_vm.cy +++ b/src/builtins/builtins_vm.cy @@ -177,7 +177,7 @@ type List[dyn] _ --| 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. -@host func List.fill[](val T, n int) List[T] +@host func List.fill(val #T, n int) List[T] @host type ListIterator[T type] _: @host func next() ?T @@ -443,21 +443,21 @@ 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(val #T) Future[T]: return Future.complete_(val, (Future[T]).id()) -@host -func Future.complete_[](val T, ret_type int) Future[T] +@host -func Future.complete_(val #T, ret_type int) Future[T] -func Future.new[]() Future[T]: - return Future.new_[T]((Future[T]).id()) +func Future.new(#T type) Future[T]: + return Future.new_(T, (Future[T]).id()) -@host -func Future.new_[](ret_type int) Future[T] +@host -func Future.new_(#T type, ret_type int) Future[T] @host type FutureResolver[T type] _: @host func complete(val T) void @host func future() Future[T] -func FutureResolver.new[]() FutureResolver[T]: - return FutureResolver.new_[T]((Future[T]).id(), (FutureResolver[T]).id()) +func FutureResolver.new(#T type) FutureResolver[T]: + return FutureResolver.new_(T, (Future[T]).id(), (FutureResolver[T]).id()) -@host -func FutureResolver.new_[](future_t int, ret_t int) FutureResolver[T] \ No newline at end of file +@host -func FutureResolver.new_(#T type, future_t int, ret_t int) FutureResolver[T] \ No newline at end of file diff --git a/src/capi.zig b/src/capi.zig index 952fc11c1..96e82c277 100644 --- a/src/capi.zig +++ b/src/capi.zig @@ -110,13 +110,14 @@ pub inline fn CORE_TYPE(type_id: TypeId) HostType { }}, }; } -pub inline fn CORE_TYPE_EXT(type_id: TypeId, get_children: GetChildrenFn, finalizer: FinalizerFn) HostType { +pub inline fn CORE_TYPE_EXT(type_id: TypeId, get_children: GetChildrenFn, finalizer: FinalizerFn, load_all_methods: bool) HostType { return HostType{ .type = c.CL_BIND_TYPE_CORE_CUSTOM, .data = .{ .core_custom = .{ .type_id = type_id, .get_children = get_children, .finalizer = finalizer, + .load_all_methods = load_all_methods, }}, }; } diff --git a/src/cgen.zig b/src/cgen.zig index 76e06f325..6569b3d7f 100644 --- a/src/cgen.zig +++ b/src/cgen.zig @@ -707,7 +707,7 @@ fn genHead(c: *Compiler, w: std.ArrayListUnmanaged(u8).Writer, chunks: []Chunk) const params = base.sema.getFuncSig(func.funcSigId).params(); if (params.len > 0) { for (params) |param| { - try w.print(", {}", .{ try chunk.cTypeName(param) }); + try w.print(", {}", .{ try chunk.cTypeName(param.type) }); } } try w.writeAll(");\n"); @@ -719,7 +719,7 @@ fn genHead(c: *Compiler, w: std.ArrayListUnmanaged(u8).Writer, chunks: []Chunk) const params = funcSig.params(); if (params.len > 0) { for (params) |param| { - try w.print(", {}", .{ try chunk.cTypeName(param) }); + try w.print(", {}", .{ try chunk.cTypeName(param.type) }); } } try w.writeAll(");\n"); diff --git a/src/chunk.zig b/src/chunk.zig index 79155ba70..2ed5d64a5 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -125,9 +125,8 @@ pub const Chunk = struct { /// Includes lambdas which are not linked from a named sym. funcs: std.ArrayListUnmanaged(*cy.Func), - /// This is used for lookups when resolving const expressions. - cur_template_params: std.StringHashMapUnmanaged(cy.Value), - cur_template_ctx: ?sema.TemplateContext, + /// Reference the current resolve context. + resolve_stack: std.ArrayListUnmanaged(sema.ResolveContext), /// /// Codegen pass @@ -277,8 +276,7 @@ pub const Chunk = struct { }, .syms = .{}, .funcs = .{}, - .cur_template_params = .{}, - .cur_template_ctx = null, + .resolve_stack = .{}, .in_ct_expr = false, .host_types = .{}, .host_funcs = .{}, @@ -328,7 +326,6 @@ pub const Chunk = struct { self.symInitDeps.deinit(self.alloc); self.symInitInfos.deinit(self.alloc); self.curInitingSymDeps.deinit(self.alloc); - self.cur_template_params.deinit(self.alloc); self.typeStack.deinit(self.alloc); self.valueStack.deinit(self.alloc); @@ -339,6 +336,7 @@ pub const Chunk = struct { self.listDataStack.deinit(self.alloc); self.genIrLocalMapStack.deinit(self.alloc); self.genLocalStack.deinit(self.alloc); + self.resolve_stack.deinit(self.alloc); if (cy.hasJIT) { self.tempTypeRefs.deinit(self.alloc); @@ -354,17 +352,18 @@ pub const Chunk = struct { self.sym_cache.deinit(self.alloc); self.use_alls.deinit(self.alloc); + for (self.funcs.items) |func| { + func.deinit(self.alloc); + self.alloc.destroy(func); + } + self.funcs.deinit(self.alloc); + // Deinit chunk syms. for (self.syms.items) |sym| { sym.destroy(self.vm, self.alloc); } self.syms.deinit(self.alloc); - for (self.funcs.items) |func| { - self.alloc.destroy(func); - } - self.funcs.deinit(self.alloc); - for (self.variantFuncSyms.items) |func| { self.alloc.destroy(func); } diff --git a/src/compiler.zig b/src/compiler.zig index e17411835..d9ae95fee 100644 --- a/src/compiler.zig +++ b/src/compiler.zig @@ -134,6 +134,9 @@ pub const Compiler = struct { for (chunk.syms.items) |sym| { sym.deinitRetained(self.vm); } + for (chunk.funcs.items) |func| { + func.deinitRetained(self.vm); + } } } @@ -340,14 +343,16 @@ pub const Compiler = struct { // Pass through type syms. for (self.newTypes()) |*type_e| { - if (type_e.sym.getMod().?.getSym("$get") != null) { - type_e.has_get_method = true; - } - if (type_e.sym.getMod().?.getSym("$set") != null) { - type_e.has_set_method = true; - } - if (type_e.sym.getMod().?.getSym("$initPair") != null) { - type_e.has_init_pair_method = true; + if (type_e.sym.getMod()) |mod| { + if (mod.getSym("$get") != null) { + type_e.has_get_method = true; + } + if (mod.getSym("$set") != null) { + type_e.has_set_method = true; + } + if (mod.getSym("$initPair") != null) { + type_e.has_init_pair_method = true; + } } switch (type_e.sym.type) { .object_t => { @@ -419,7 +424,7 @@ pub const Compiler = struct { if (func.type != .userFunc) { continue; } - if (func.isMethod) { + if (func.isMethod()) { try sema.methodDecl(chunk, func); } else { try sema.funcDecl(chunk, func); @@ -556,6 +561,9 @@ fn performChunkSema(self: *Compiler, chunk: *cy.Chunk) !void { try chunk.ir.pushStmtBlock2(chunk.alloc, chunk.rootStmtBlock); if (chunk == self.main_chunk) { + try sema.pushResolveContext(chunk); + defer sema.popResolveContext(chunk); + _ = try sema.semaMainBlock(self, chunk); } // Top level declarations only. @@ -577,7 +585,7 @@ fn performChunkSemaDecls(c: *cy.Chunk) !void { continue; } log.tracev("sema func: {s}", .{func.name()}); - if (func.isMethod) { + if (func.isMethod()) { try sema.methodDecl(c, func); } else { try sema.funcDecl(c, func); @@ -834,7 +842,9 @@ fn reserveSyms(self: *Compiler, core_sym: *cy.sym.Chunk) !void{ } }, .template => { - _ = try sema.declareTemplate(chunk, node.cast(.template)); + const decl = node.cast(.template); + const sym = try sema.declareTemplate(chunk, decl); + _ = sym; }, .specialization => { // For now template declaration must already be declared. @@ -886,7 +896,10 @@ fn reserveSyms(self: *Compiler, core_sym: *cy.sym.Chunk) !void{ }; try template.variant_cache.put(chunk.alloc, args_dupe, variant); }, - else => return error.Unsupported, + else => { + log.tracev("{}", .{decl.decl.type()}); + return error.Unsupported; + } } }, .staticDecl => { @@ -997,6 +1010,7 @@ fn reserveCoreTypes(self: *Compiler) !void { bt.MetaType, bt.Range, bt.Table, + bt.CTInfer, }; for (type_ids) |type_id| { @@ -1005,8 +1019,17 @@ fn reserveCoreTypes(self: *Compiler) !void { } std.debug.assert(self.sema.types.items.len == cy.types.BuiltinEnd); + + self.sema.types.items[bt.CTInfer].kind = .bare; + self.sema.types.items[bt.CTInfer].sym = @ptrCast(&CTInferType); + self.sema.types.items[bt.CTInfer].info.ct_infer = true; } +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"); @@ -1087,7 +1110,7 @@ fn resolveSyms(self: *Compiler) !void { }, .enum_t => { const enum_t = sym.cast(.enum_t); - try sema.declareEnumMembers(chunk, @ptrCast(sym), enum_t.decl); + try sema.resolveEnumType(chunk, @ptrCast(sym), enum_t.decl); }, .distinct_t => { _ = try sema.resolveDistinctType(chunk, @ptrCast(sym)); diff --git a/src/cte.zig b/src/cte.zig index e3757a972..35b91db0f 100644 --- a/src/cte.zig +++ b/src/cte.zig @@ -9,15 +9,11 @@ const ast = cy.ast; const cte = @This(); pub fn expandTemplateOnCallExpr(c: *cy.Chunk, node: *ast.CallExpr) !*cy.Sym { - const calleeRes = try c.semaExprSkipSym(node.callee); - if (calleeRes.resType != .sym) { + var callee = try cy.sema.resolveSym(c, node.callee); + if (callee.type != .template) { return c.reportErrorFmt("Expected template symbol.", &.{}, node.callee); } - const sym = calleeRes.data.sym; - if (sym.type != .template) { - return c.reportErrorFmt("Expected template symbol.", &.{}, node.callee); - } - return cte.expandTemplateOnCallArgs(c, sym.cast(.template), node.args, @ptrCast(node)); + return cte.expandTemplateOnCallArgs(c, callee.cast(.template), node.args, @ptrCast(node)); } pub fn pushNodeValues(c: *cy.Chunk, args: []const *ast.Node) !void { @@ -42,32 +38,97 @@ pub fn expandTemplateOnCallArgs(c: *cy.Chunk, template: *cy.sym.Template, args: } c.valueStack.items.len = valueStart; } + try pushNodeValues(c, args); + const argTypes = c.typeStack.items[typeStart..]; const arg_vals = c.valueStack.items[valueStart..]; - return expandTemplate(c, template, arg_vals, argTypes) catch |err| { - if (err == error.IncompatSig) { - const sig = c.sema.getFuncSig(template.sigId); - const params_s = try c.sema.allocFuncParamsStr(sig.params(), c); - defer c.alloc.free(params_s); - return c.reportErrorFmt( - \\Expected template signature `{}[{}]`. - , &.{v(template.head.name()), v(params_s)}, node); - } else { - return err; - } - }; + // Check against template signature. + if (!cy.types.isTypeFuncSigCompat(c.compiler, @ptrCast(argTypes), .not_void, template.sigId)) { + const sig = c.sema.getFuncSig(template.sigId); + const params_s = try c.sema.allocFuncParamsStr(sig.params(), c); + defer c.alloc.free(params_s); + return c.reportErrorFmt( + \\Expected template signature `{}[{}]`. + , &.{v(template.head.name()), v(params_s)}, node); + } + + return expandTemplate(c, template, arg_vals); } -pub fn expandTemplate(c: *cy.Chunk, template: *cy.sym.Template, args: []const cy.Value, arg_types: []const cy.TypeId) !*cy.Sym { - const root_template = template.root(); +pub fn expandFuncTemplateOnCallArgs(c: *cy.Chunk, template: *cy.Func, args: []const *ast.Node, node: *ast.Node) !*cy.Func { + // Accumulate compile-time args. + const typeStart = c.typeStack.items.len; + const valueStart = c.valueStack.items.len; + defer { + c.typeStack.items.len = typeStart; + + // Values need to be released. + const values = c.valueStack.items[valueStart..]; + for (values) |val| { + c.vm.release(val); + } + c.valueStack.items.len = valueStart; + } + + try pushNodeValues(c, args); + + const argTypes = c.typeStack.items[typeStart..]; + const arg_vals = c.valueStack.items[valueStart..]; // Check against template signature. - if (!cy.types.isTypeFuncSigCompat(c.compiler, @ptrCast(arg_types), .not_void, root_template.sigId)) { - return error.IncompatSig; + 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); } + 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; + + // Ensure variant func. + const res = try template.variant_cache.getOrPut(c.alloc, args); + if (!res.found_existing) { + // Dupe args and retain + const args_dupe = try c.alloc.dupe(cy.Value, args); + for (args_dupe) |param| { + c.vm.retain(param); + } + + // Generate variant type. + const variant = try c.alloc.create(cy.sym.FuncVariant); + variant.* = .{ + .args = args_dupe, + .template = tfunc.data.template, + .func = undefined, + }; + + const tchunk = tfunc.chunk(); + const new_func = try sema.reserveFuncTemplateVariant(tchunk, tfunc, tfunc.decl, variant); + variant.func = new_func; + res.key_ptr.* = args_dupe; + res.value_ptr.* = variant; + + // Allow circular reference by resolving after the new symbol has been added to the cache. + try sema.resolveFuncVariant(tchunk, new_func); + + return new_func; + } + const variant = res.value_ptr.*; + return variant.func; +} + +pub fn expandTemplate(c: *cy.Chunk, template: *cy.sym.Template, args: []const cy.Value) !*cy.Sym { + const root_template = template.root(); + // Ensure variant type. const res = try root_template.variant_cache.getOrPut(c.alloc, args); if (!res.found_existing) { @@ -77,6 +138,24 @@ pub fn expandTemplate(c: *cy.Chunk, template: *cy.sym.Template, args: []const cy c.vm.retain(param); } + var ct_infer = false; + var ct_ref = false; + for (args) |arg| { + if (arg.getTypeId() == bt.Type) { + const type_id = arg.asHeapObject().type.type; + switch (type_id) { + bt.CTInfer => { + ct_infer = true; + }, + else => { + const type_e = c.sema.types.items[type_id]; + ct_infer = ct_infer or type_e.info.ct_infer; + ct_ref = ct_ref or type_e.info.ct_ref; + }, + } + } + } + // Generate variant type. const variant = try c.alloc.create(cy.sym.Variant); variant.* = .{ @@ -91,6 +170,10 @@ pub fn expandTemplate(c: *cy.Chunk, template: *cy.sym.Template, args: []const cy res.key_ptr.* = args_dupe; res.value_ptr.* = variant; + 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_ref; + // Allow circular reference by resolving after the new symbol has been added to the cache. try sema.resolveTemplateVariant(c, root_template, new_sym); @@ -179,7 +262,7 @@ const CtValue = struct { value: cy.Value, }; -fn nodeToCtValue(c: *cy.Chunk, node: *ast.Node) anyerror!CtValue { +pub fn nodeToCtValue(c: *cy.Chunk, node: *ast.Node) anyerror!CtValue { // TODO: Evaluate const expressions. const sym = try sema.resolveSym(c, node); if (sym.getStaticType()) |type_id| { diff --git a/src/include/cyber.h b/src/include/cyber.h index 220e28e4a..ba398f38a 100644 --- a/src/include/cyber.h +++ b/src/include/cyber.h @@ -222,6 +222,7 @@ typedef struct CLHostType { CLGetChildrenFn get_children; // Pointer to callback or null. CLFinalizerFn finalizer; + bool load_all_methods; } core_custom; struct { // If not null, the created runtime type id will be written to `outTypeId`. diff --git a/src/lib.zig b/src/lib.zig index 4c3aba453..12d081303 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -410,7 +410,7 @@ export fn clDeclareFunc(mod: c.Sym, name: [*:0]const u8, params: [*]const cy.Typ var symName: []const u8 = std.mem.sliceTo(name, 0); var nameOwned = false; const chunk = modSym.cast(.chunk).getMod().chunk; - const funcSigId = chunk.sema.ensureFuncSig(params[0..numParams], retType) catch cy.fatal(); + const funcSigId = chunk.sema.ensureFuncSig(@ptrCast(params[0..numParams]), retType) catch cy.fatal(); if (modSym.getMod().?.getSym(symName) == null) { symName = chunk.alloc.dupe(u8, symName) catch fatal(); nameOwned = true; @@ -455,7 +455,13 @@ export fn clExpandTemplateType(ctemplate: c.Sym, args_ptr: [*]const cy.Value, na chunk.typeStack.append(chunk.alloc, arg.getTypeId()) catch cy.fatal(); } const arg_types = chunk.typeStack.items[typeStart..]; - const sym = cy.cte.expandTemplate(chunk, template, args, arg_types) catch cy.fatal(); + + // Check against template signature. + if (!cy.types.isTypeFuncSigCompat(chunk.compiler, @ptrCast(arg_types), .not_void, template.sigId)) { + cy.fatal(); + } + + const sym = cy.cte.expandTemplate(chunk, template, args) catch cy.fatal(); return sym.getStaticType().?; } @@ -549,7 +555,7 @@ export fn clNewFuncDyn(vm: *cy.VM, numParams: u32, func: c.FuncFn) Value { } export fn clNewFunc(vm: *cy.VM, params: [*]const cy.TypeId, numParams: u32, retType: cy.TypeId, func: c.FuncFn) Value { - const funcSigId = vm.sema.ensureFuncSig(params[0..numParams], retType) catch fatal(); + const funcSigId = vm.sema.ensureFuncSig(@ptrCast(params[0..numParams]), retType) catch fatal(); const funcSig = vm.sema.funcSigs.items[funcSigId]; return vm.allocHostFunc(@ptrCast(func), numParams, funcSigId, null, funcSig.reqCallTypeCheck) catch fatal(); } diff --git a/src/module.zig b/src/module.zig index 58aa33479..6916197a3 100644 --- a/src/module.zig +++ b/src/module.zig @@ -95,7 +95,7 @@ pub const Module = struct { } const func = sym.cast(.func).first; - if (!func.isMethod) { + if (!func.isMethod()) { return null; } const trait_sig = c.sema.getFuncSig(member.func.funcSigId); @@ -103,8 +103,8 @@ pub const Module = struct { const trait_params = trait_sig.params()[1..]; const func_params = func_sig.params()[1..]; - for (trait_params, 0..) |type_id, i| { - if (type_id != func_params[i]) { + for (trait_params, 0..) |trait_param, i| { + if (trait_param.type != func_params[i].type) { return null; } } @@ -351,10 +351,26 @@ pub const ChunkExt = struct { func.funcSigId = funcSigId; func.retType = func_sig.getRetType(); func.reqCallTypeCheck = func_sig.reqCallTypeCheck; - func.numParams = @intCast(func_sig.paramLen); + func.numParams = @intCast(func_sig.params_len); return func; } + 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, parent, 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, is_variant: bool, ) !*cy.Func { @@ -434,7 +450,7 @@ pub const ChunkExt = struct { func.funcSigId = func_sig_id; func.retType = func_sig.getRetType(); func.reqCallTypeCheck = func_sig.reqCallTypeCheck; - func.numParams = @intCast(func_sig.paramLen); + func.numParams = @intCast(func_sig.params_len); } pub fn getOptResolvedSym(_: *cy.Chunk, modSym: *cy.Sym, name: []const u8) !?*cy.Sym { @@ -474,11 +490,19 @@ pub const ChunkExt = struct { const mod_name = try cy.sym.formatSymName(c.sema, &cy.tempBuf, modSym, .{ .from = c }); return c.reportErrorFmt("Can not access `{}` from parent `{}`. Parent is not a module.", &.{v(name), v(mod_name)}, nodeId); }; - const sym = mod.getSym(name) orelse { - const mod_name = try cy.sym.formatSymName(c.sema, &cy.tempBuf, modSym.resolved(), .{ .from = c }); - return c.reportErrorFmt("Can not find the symbol `{}` in `{}`.", &.{v(name), v(mod_name)}, nodeId); - }; - return sym; + if (mod.getSym(name)) |sym| { + return sym; + } + + // TODO: A type variant sym's module should cache expanded methods. + if (modSym.getVariant()) |variant| { + if (variant.root_template.getMod().getSym(name)) |sym| { + return sym; + } + } + + const mod_name = try cy.sym.formatSymName(c.sema, &cy.tempBuf, modSym.resolved(), .{ .from = c }); + return c.reportErrorFmt("Can not find the symbol `{}` in `{}`.", &.{v(name), v(mod_name)}, nodeId); } pub fn getResolvedDistinctSym( @@ -513,6 +537,7 @@ pub const ChunkExt = struct { .distinct_t, .template, .placeholder, + .dummy_t, .enumMember => { return sym; }, @@ -583,6 +608,7 @@ pub const ChunkExt = struct { .object_t, .trait_t, .custom_t, + .dummy_t, .enum_t, .chunk, .bool_t, diff --git a/src/parser.zig b/src/parser.zig index 701d18e93..6ba7dfc24 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -486,6 +486,7 @@ pub const Parser = struct { .name_pos = name.pos, .name_len = name.len, .typeSpec = null, + .ct_param = false, }); } @@ -535,6 +536,44 @@ pub const Parser = struct { return self.ast.dupeNodes(self.node_stack.items[field_start..]); } + fn parseFuncParam(self: *Parser, typed: bool, allow_self: bool) !*ast.FuncParam { + var name_token = self.peek(); + var ct_param = false; + if (name_token.tag() != .ident) { + if (name_token.tag() == .pound) { + self.advance(); + name_token = self.peek(); + ct_param = true; + if (self.peek().tag() != .ident) { + return self.reportError("Expected param name.", &.{}); + } + } + } + + self.advance(); + var type_spec: ?*ast.Node = null; + if (typed) { + if (try self.parseOptTypeSpec(false)) |spec| { + type_spec = spec; + } else { + if (allow_self) { + const param_name = self.ast.src[name_token.pos()..name_token.data.end_pos]; + if (!std.mem.eql(u8, param_name, "self")) { + return self.reportError("Expected param type.", &.{}); + } + } else { + return self.reportError("Expected param type.", &.{}); + } + } + } + return self.ast.newNode(.funcParam, .{ + .name_pos = name_token.pos(), + .name_len = name_token.data.end_pos - name_token.pos(), + .typeSpec = type_spec, + .ct_param = ct_param, + }); + } + /// 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, typed: bool, comptime template: bool) ![]*ast.FuncParam { @@ -547,43 +586,17 @@ pub const Parser = struct { try self.node_stack.appendSlice(self.alloc, @ptrCast(pre_params)); } - var token = self.peek(); - if (token.tag() == CloseDelim) { + if (self.peek().tag() == CloseDelim) { self.advance(); return &.{}; } - if (token.tag() != .ident) { - return self.reportError("Unexpected token in function param list.", &.{}); - } - // Parse params. - var start = self.next_pos; - var name_token = self.tokens[start]; - - self.advance(); - var type_spec: ?*ast.Node = null; - if (typed) { - if (try self.parseOptTypeSpec(false)) |spec| { - type_spec = spec; - } else { - const param_name = self.ast.src[token.pos()..token.data.end_pos]; - if (!std.mem.eql(u8, param_name, "self")) { - return self.reportError("Expected param type.", &.{}); - } - } - } - - var param = try self.ast.newNode(.funcParam, .{ - .name_pos = name_token.pos(), - .name_len = name_token.data.end_pos - name_token.pos(), - .typeSpec = type_spec, - }); + var param = try self.parseFuncParam(typed, true); try self.pushNode(@ptrCast(param)); while (true) { - token = self.peek(); - switch (token.tag()) { + switch (self.peek().tag()) { .comma => { self.advance(); }, @@ -593,27 +606,7 @@ pub const Parser = struct { }, else => return self.reportError("Expected `,`.", &.{}), } - - token = self.peek(); - start = self.next_pos; - if (token.tag() != .ident) { - return self.reportError("Expected param identifier.", &.{}); - } - - name_token = self.tokens[start]; - self.advance(); - - if (typed) { - type_spec = (try self.parseOptTypeSpec(false)) orelse { - return self.reportError("Expected param type.", &.{}); - }; - } - - param = try self.ast.newNode(.funcParam, .{ - .name_pos = name_token.pos(), - .name_len = name_token.data.end_pos - name_token.pos(), - .typeSpec = type_spec, - }); + param = try self.parseFuncParam(typed, false); try self.pushNode(@ptrCast(param)); } const params = self.node_stack.items[param_start..]; @@ -1163,7 +1156,11 @@ pub const Parser = struct { var opt_template: ?*ast.Node = null; if (self.peek().tag() == .left_bracket) { - opt_template = try self.parseTemplateOrSpecialization(); + const args = try self.parseArrayLiteral2(); + opt_template = try self.ast.newNodeErase(.specialization, .{ + .args = args, + .decl = undefined, + }); try self.staticDecls.append(self.alloc, @ptrCast(opt_template)); self.consumeWhitespaceTokens(); } diff --git a/src/sema.zig b/src/sema.zig index d9656b100..d2dcee124 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -1102,6 +1102,9 @@ const InitFieldResult = struct { }; pub fn declareTemplate(c: *cy.Chunk, node: *ast.TemplateDecl) !*cy.sym.Template { + try pushResolveContext(c); + defer popResolveContext(c); + var name_n: *ast.Node = undefined; var decl_t: cy.sym.SymType = undefined; switch (node.decl.type()) { @@ -1117,10 +1120,6 @@ pub fn declareTemplate(c: *cy.Chunk, node: *ast.TemplateDecl) !*cy.sym.Template name_n = node.decl.cast(.custom_decl).name; decl_t = .custom_t; }, - .funcDecl => { - name_n = node.decl.cast(.funcDecl).name; - decl_t = .func; - }, else => { return c.reportErrorFmt("Unsupported type template.", &.{}, @ptrCast(node)); } @@ -1137,44 +1136,7 @@ pub fn declareTemplate(c: *cy.Chunk, node: *ast.TemplateDecl) !*cy.sym.Template params = decl_path.parent.cast(.template).params; sigId = decl_path.parent.cast(.template).sigId; } - const sym = try c.declareTemplate(decl_path.parent, decl_path.name.base_name, sigId, is_root, params, decl_t, node.decl, node); - if (decl_t == .func) { - const infer_from_func_params = try c.alloc.alloc(u8, params.len); - @memset(infer_from_func_params, cy.NullU8); - const child_decl = node.decl.cast(.funcDecl); - - for (child_decl.params, 0..) |param, i| { - if (param.typeSpec == null) { - break; - } - if (param.typeSpec.?.type() != .ident) { - continue; - } - const pname = c.ast.nodeString(@ptrCast(param.typeSpec)); - const tidx = sym.indexOfParam(pname) orelse { - continue; - }; - if (infer_from_func_params[tidx] != cy.NullU8) { - // Already a mapping. - continue; - } - infer_from_func_params[tidx] = @intCast(i); - } - // Verify each template param has a mapping. - var infer_min_params: u8 = 0; - for (infer_from_func_params) |func_idx| { - if (func_idx == cy.NullU8) { - infer_min_params = cy.NullU8; - break; - } - if (func_idx + 1 > infer_min_params) { - infer_min_params = func_idx + 1; - } - } - sym.infer_min_params = infer_min_params; - sym.infer_from_func_params = infer_from_func_params; - } - return sym; + return c.declareTemplate(decl_path.parent, decl_path.name.base_name, sigId, is_root, params, decl_t, node.decl, node); } /// Explicit `decl` for specialization declarations. @@ -1225,7 +1187,7 @@ fn resolveCustomType(c: *cy.Chunk, sym: *cy.sym.CustomType, decl: *ast.CustomDec } fn resolveCustomType2(c: *cy.Chunk, custom_t: *cy.sym.CustomType, - get_children: C.GetChildrenFn, finalizer: C.FinalizerFn, opt_type: ?cy.TypeId, pre: bool) !void { + get_children: C.GetChildrenFn, finalizer: C.FinalizerFn, opt_type: ?cy.TypeId, pre: bool, load_all_methods: bool) !void { const typeid = opt_type orelse try c.sema.pushType(); custom_t.type = typeid; c.compiler.sema.types.items[typeid] = .{ @@ -1233,6 +1195,7 @@ fn resolveCustomType2(c: *cy.Chunk, custom_t: *cy.sym.CustomType, .kind = .custom, .info = .{ .custom_pre = pre, + .load_all_methods = load_all_methods, }, .data = .{ .custom = .{ .getChildrenFn = get_children, @@ -1249,6 +1212,7 @@ fn resolveCustomTypeResult(c: *cy.Chunk, custom_t: *cy.sym.CustomType, host_t: C host_t.data.core_custom.finalizer, host_t.data.core_custom.type_id, false, + host_t.data.core_custom.load_all_methods, ); }, C.BindTypeCustom => { @@ -1257,6 +1221,7 @@ fn resolveCustomTypeResult(c: *cy.Chunk, custom_t: *cy.sym.CustomType, host_t: C host_t.data.custom.finalizer, null, host_t.data.custom.pre, + false, ); if (host_t.data.custom.out_type_id) |out_type_id| { out_type_id.* = custom_t.type; @@ -1298,6 +1263,9 @@ pub fn reserveTypeAlias(c: *cy.Chunk, node: *ast.TypeAliasDecl) !*cy.sym.TypeAli } pub fn resolveTypeAlias(c: *cy.Chunk, sym: *cy.sym.TypeAlias) !void { + try sema.pushResolveContext(c); + defer sema.popResolveContext(c); + sym.type = try cy.sema.resolveTypeSpecNode(c, sym.decl.typeSpec); sym.sym = c.sema.types.items[sym.type].sym; // Merge modules. @@ -1360,7 +1328,7 @@ pub fn declareUseImport(c: *cy.Chunk, node: *ast.ImportStmt) !void { c.compiler.global_sym = global; const func = try c.reserveHostFunc(@ptrCast(c.compiler.main_chunk.sym), "$getGlobal", null, false, false); - const func_sig = try c.sema.ensureFuncSig(&.{ bt.Map, bt.Any }, bt.Dyn); + const func_sig = try c.sema.ensureFuncSigRt(&.{ bt.Map, bt.Any }, bt.Dyn); try c.resolveHostFunc(func, func_sig, cy.bindings.getGlobal); c.compiler.get_global = func; } @@ -1426,6 +1394,17 @@ pub fn reserveEnum(c: *cy.Chunk, node: *ast.EnumDecl) !*cy.sym.EnumType { return c.reserveEnumType(@ptrCast(c.sym), name, node.isChoiceType, node); } +pub fn resolveEnumType(c: *cy.Chunk, sym: *cy.sym.EnumType, decl: *ast.EnumDecl) !void { + if (sym.variant != null) { + try pushVariantResolveContext(c, sym.variant.?); + } else { + try pushResolveContext(c); + } + defer popResolveContext(c); + + try declareEnumMembers(c, sym, decl); +} + /// Explicit `decl` node for distinct type declarations. Must belong to `c`. pub fn declareEnumMembers(c: *cy.Chunk, sym: *cy.sym.EnumType, decl: *ast.EnumDecl) !void { if (sym.isResolved()) { @@ -1493,33 +1472,102 @@ pub fn getHostTypeId(c: *cy.Chunk, type_sym: *cy.Sym, opt_name: ?[]const u8, nod return host_t.data.core_decl.type_id; } -pub const TemplateContext = struct { - template: *cy.sym.Template, - variant: *cy.sym.Variant, +pub const ResolveContext = struct { + 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, + + /// Compile-time params in this context. + /// TODO: Avoid hash map if 2 or fewer ct params. + ct_params: std.StringHashMapUnmanaged(cy.Value), + + fn deinit(self: *ResolveContext, c: *cy.Chunk) void { + var iter = self.ct_params.iterator(); + while (iter.next()) |e| { + c.vm.release(e.value_ptr.*); + } + 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); + if (res.found_existing) { + return error.DuplicateParam; + } + res.value_ptr.* = val; + } }; -fn pushTemplateContext(c: *cy.Chunk, variant: *cy.sym.Variant) !?TemplateContext { +fn pushFuncVariantResolveContext(c: *cy.Chunk, variant: *cy.sym.FuncVariant) !void { + var new = ResolveContext{ + .has_ct_params = true, + .ct_params = .{}, + }; + for (variant.template.params, 0..) |param, i| { + const arg = variant.args[i]; + c.vm.retain(arg); + try new.setCtParam(c, param.name, arg); + } + try c.resolve_stack.append(c.alloc, new); +} + +fn pushVariantResolveContext(c: *cy.Chunk, variant: *cy.sym.Variant) !void { + var new = ResolveContext{ + .has_ct_params = true, + .ct_params = .{}, + }; const template = variant.root_template; for (template.params, 0..) |param, i| { const arg = variant.args[i]; - try c.cur_template_params.put(c.alloc, param.name, arg); + c.vm.retain(arg); + try new.setCtParam(c, param.name, arg); } - defer c.cur_template_ctx = .{ .template = template, .variant = variant }; - return c.cur_template_ctx; + try c.resolve_stack.append(c.alloc, new); } -fn popTemplateContext(c: *cy.Chunk, opt_ctx: ?TemplateContext) !void { - if (std.meta.eql(c.cur_template_ctx, opt_ctx)) { - return; - } - c.cur_template_params.clearRetainingCapacity(); - if (opt_ctx) |ctx| { - for (ctx.template.params, 0..) |param, i| { - const arg = ctx.variant.args[i]; - try c.cur_template_params.put(c.alloc, param.name, arg); - } +pub fn pushResolveContext(c: *cy.Chunk) !void { + const new = ResolveContext{ + .has_ct_params = false, + .ct_params = .{}, + }; + try c.resolve_stack.append(c.alloc, new); +} + +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); +} + +pub fn popResolveContext(c: *cy.Chunk) void { + const last = &c.resolve_stack.items[c.resolve_stack.items.len-1]; + last.deinit(c); + c.resolve_stack.items.len -= 1; +} + +/// 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.FuncVariant) !*cy.sym.Func { + const sym = tfunc.sym.?; + + var func: *cy.Func = undefined; + const decl = tfunc.decl.?.cast(.funcDecl); + if (decl.stmts.len == 0) { + func = try c.createFunc(.hostFunc, @ptrCast(c.sym), sym, @ptrCast(opt_header_decl), tfunc.isMethod()); + func.data = .{ .hostFunc = .{ + .ptr = undefined, + }}; + } else { + func = try c.createFunc(.userFunc, @ptrCast(c.sym), sym, @ptrCast(opt_header_decl), tfunc.isMethod()); } - c.cur_template_ctx = opt_ctx; + func.is_implicit_method = tfunc.is_implicit_method; + func.variant = variant; + try c.variantFuncSyms.append(c.alloc, func); + + return func; } /// Allow an explicit `opt_header_decl` so that template specialization can use it to override @host attributes. @@ -1554,24 +1602,6 @@ pub fn reserveTemplateVariant(c: *cy.Chunk, template: *cy.sym.Template, opt_head const sym = try c.createEnumTypeVariant(@ptrCast(c.sym), template, decl.isChoiceType, variant); return @ptrCast(sym); }, - .func => { - const sym = try c.createFuncSym(@ptrCast(c.sym), name); - sym.variant = variant; - - var func: *cy.Func = undefined; - const decl = template.child_decl.cast(.funcDecl); - if (decl.stmts.len == 0) { - func = try c.createFunc(.hostFunc, @ptrCast(c.sym), sym, @ptrCast(decl), false); - func.data = .{ .hostFunc = .{ - .ptr = undefined, - }}; - } else { - func = try c.createFunc(.userFunc, @ptrCast(c.sym), sym, @ptrCast(decl), false); - } - try c.variantFuncSyms.append(c.alloc, func); - sym.addFunc(func); - return @ptrCast(sym); - }, else => { log.tracev("{}", .{template.kind}); return error.Unsupported; @@ -1579,76 +1609,60 @@ 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.Sym) !void { - _ = c; +/// TODO: Load methods on-demand for tree-shaking. +pub fn resolveTemplateVariant(c: *cy.Chunk, template: *cy.sym.Template, sym: *cy.sym.Sym) anyerror!void { + // 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) { + return; + } const tchunk = template.chunk(); switch (sym.type) { .object_t => { const object_t = sym.cast(.object_t); - const object_decl = template.child_decl.cast(.objectDecl); - - const prev = try pushTemplateContext(tchunk, object_t.variant.?); - defer popTemplateContext(tchunk, prev) catch @panic("error"); - try resolveObjectLikeType(tchunk, @ptrCast(sym), template.child_decl); + try pushVariantResolveContext(tchunk, object_t.variant.?); + defer popResolveContext(tchunk); + + const object_decl = template.child_decl.cast(.objectDecl); for (object_decl.funcs) |func_n| { - const func = try reserveImplicitMethod(tchunk, @ptrCast(object_t), func_n, true); - func.sym.?.variant = object_t.variant.?; - try resolveImplicitMethod(tchunk, func); + const func = try reserveImplicitMethod(tchunk, @ptrCast(object_t), func_n, false); + // func.sym.?.variant = object_t.variant.?; + try resolveImplicitMethod(tchunk, func, true); } }, .custom_t => { const custom_t = sym.cast(.custom_t); - const prev = try pushTemplateContext(tchunk, custom_t.variant.?); - defer popTemplateContext(tchunk, prev) catch @panic("error"); + try pushVariantResolveContext(tchunk, custom_t.variant.?); + defer popResolveContext(tchunk); const custom_decl = template.child_decl.cast(.custom_decl); for (custom_decl.funcs) |func_n| { - const func = try reserveImplicitMethod(tchunk, @ptrCast(custom_t), func_n, true); - func.sym.?.variant = custom_t.variant.?; - try resolveImplicitMethod(tchunk, func); - } - - // Resolve explicit functions. - var iter = template.getMod().symMap.iterator(); - while (iter.next()) |e| { - if (e.value_ptr.*.type != .template) { - return error.Unsupported; - } - const child_template = e.value_ptr.*.cast(.template); - if (child_template.kind != .func) { - return error.Unsupported; - } - - const func_decl = child_template.child_decl.cast(.funcDecl); - if (func_decl.stmts.len == 0) { - const func = try sema.reserveHostFunc2(tchunk, @ptrCast(custom_t), child_template.head.name(), func_decl, true); - func.sym.?.variant = custom_t.variant.?; - try sema.resolveFunc(tchunk, func); - } else { - const func = try sema.reserveUserFunc2(tchunk, @ptrCast(custom_t), child_template.head.name(), func_decl, true); - func.sym.?.variant = custom_t.variant.?; - try sema.resolveFunc(tchunk, func); - } + const func = try reserveImplicitMethod(tchunk, @ptrCast(custom_t), func_n, false); + // func.sym.?.variant = custom_t.variant.?; + try resolveImplicitMethod(tchunk, func, true); } + // if (type_e.info.load_all_methods) { + // var iter = template.getMod().symMap.iterator(); + // while (iter.next()) |e| { + // if (e.value_ptr.*.type != .func) continue; + // const func_sym = e.value_ptr.*.cast(.func); + // var opt_func: ?*cy.Func = func_sym.first; + // while (opt_func) |func| { + // if (func.isMethod() and func.type == .template) { + // const ct_args = custom_t.variant.?.args; + // _ = try cte.expandFuncTemplate(c, func, ct_args, custom_t.type); + // } + // opt_func = func.next; + // } + // } + // } }, .enum_t => { const enum_t = sym.cast(.enum_t); - - const prev = try pushTemplateContext(tchunk, enum_t.variant.?); - defer popTemplateContext(tchunk, prev) catch @panic("error"); - - try declareEnumMembers(tchunk, enum_t, @ptrCast(template.child_decl)); - }, - .func => { - const func = sym.cast(.func); - - const prev = try pushTemplateContext(tchunk, func.variant.?); - defer popTemplateContext(tchunk, prev) catch @panic("error"); - - try resolveFunc(tchunk, func.first); + try resolveEnumType(tchunk, enum_t, @ptrCast(template.child_decl)); }, else => { return error.Unsupported; @@ -1657,29 +1671,29 @@ pub fn resolveTemplateVariant(c: *cy.Chunk, template: *cy.sym.Template, sym: *cy } pub fn reserveTableMethods(c: *cy.Chunk, obj: *cy.sym.ObjectType) !void { - _ = try c.reserveHostFunc(@ptrCast(obj), "$get", null, true, true); - _ = try c.reserveHostFunc(@ptrCast(obj), "$set", null, true, true); - _ = try c.reserveHostFunc(@ptrCast(obj), "$index", null, true, true); - _ = try c.reserveHostFunc(@ptrCast(obj), "$setIndex", null, true, true); + _ = try c.reserveHostFunc(@ptrCast(obj), "$get", null, true, false); + _ = try c.reserveHostFunc(@ptrCast(obj), "$set", null, true, false); + _ = try c.reserveHostFunc(@ptrCast(obj), "$index", null, true, false); + _ = try c.reserveHostFunc(@ptrCast(obj), "$setIndex", null, true, false); } pub fn resolveTableMethods(c: *cy.Chunk, obj: *cy.sym.ObjectType) !void { const mod = obj.getMod(); const get = mod.getSym("$get").?.cast(.func).first; - var sig = try c.sema.ensureFuncSig(&.{ obj.type, bt.String }, bt.Dyn); + var sig = try c.sema.ensureFuncSigRt(&.{ obj.type, bt.String }, bt.Dyn); try c.resolveHostFunc(get, sig, cy.bindings.tableGet); const set = mod.getSym("$set").?.cast(.func).first; - sig = try c.sema.ensureFuncSig(&.{ obj.type, bt.String, bt.Any }, bt.Void); + sig = try c.sema.ensureFuncSigRt(&.{ obj.type, bt.String, bt.Any }, bt.Void); try c.resolveHostFunc(set, sig, cy.builtins.zErrFunc(cy.bindings.tableSet)); const index = mod.getSym("$index").?.cast(.func).first; - sig = try c.sema.ensureFuncSig(&.{ obj.type, bt.Any }, bt.Dyn); + sig = try c.sema.ensureFuncSigRt(&.{ obj.type, bt.Any }, bt.Dyn); try c.resolveHostFunc(index, sig, cy.bindings.tableIndex); const set_index = mod.getSym("$setIndex").?.cast(.func).first; - sig = try c.sema.ensureFuncSig(&.{ obj.type, bt.Any, bt.Any }, bt.Void); + sig = try c.sema.ensureFuncSigRt(&.{ obj.type, bt.Any, bt.Any }, bt.Void); try c.resolveHostFunc(set_index, sig, cy.builtins.zErrFunc(cy.bindings.tableSet)); } @@ -1781,6 +1795,9 @@ pub fn reserveStruct(c: *cy.Chunk, node: *ast.ObjectDecl) !*cy.sym.ObjectType { } pub fn resolveDistinctType(c: *cy.Chunk, distinct_t: *cy.sym.DistinctType) !*cy.Sym { + try sema.pushResolveContext(c); + defer sema.popResolveContext(c); + const decl = distinct_t.decl; const target_t = try resolveTypeSpecNode(c, decl.target); @@ -1884,6 +1901,13 @@ pub fn resolveStructTypeId(c: *cy.Chunk, struct_t: *cy.sym.ObjectType, opt_type: } pub fn resolveObjectLikeType(c: *cy.Chunk, object_like: *cy.Sym, decl: *ast.Node) !void { + if (object_like.getVariant()) |variant| { + try pushVariantResolveContext(c, variant); + } else { + try pushResolveContext(c); + } + defer popResolveContext(c); + try resolveObjectFields(c, object_like, decl); if (object_like.type == .object_t) { const object_t = object_like.cast(.object_t); @@ -1989,18 +2013,43 @@ pub fn reserveHostFunc2(c: *cy.Chunk, parent: *cy.Sym, name: []const u8, node: * return c.reportErrorFmt("`{}` is not a host function.", &.{v(name)}, @ptrCast(node)); } +pub fn resolveFuncVariant(c: *cy.Chunk, func: *cy.Func) !void { + switch (func.type) { + .hostFunc => { + if (func.is_implicit_method) { + try sema.resolveImplicitMethodVariant(c, func); + } else { + try sema.resolveHostFuncVariant(c, func); + } + }, + .userFunc => { + if (func.is_implicit_method) { + try sema.resolveImplicitMethodVariant(c, func); + } else { + try sema.resolveUserFuncVariant(c, func); + } + }, + .trait => { + return error.TODO; + // try sema.resolveImplicitTraitMethod(c, func); + }, + .template, + .userLambda => {}, + } +} + pub fn resolveFunc(c: *cy.Chunk, func: *cy.Func) !void { switch (func.type) { .hostFunc => { if (func.is_implicit_method) { - try sema.resolveImplicitMethod(c, func); + try sema.resolveImplicitMethod(c, func, false); } else { try sema.resolveHostFunc(c, func); } }, .userFunc => { if (func.is_implicit_method) { - try sema.resolveImplicitMethod(c, func); + try sema.resolveImplicitMethod(c, func, false); } else { try sema.resolveUserFunc(c, func); } @@ -2008,6 +2057,7 @@ pub fn resolveFunc(c: *cy.Chunk, func: *cy.Func) !void { .trait => { try sema.resolveImplicitTraitMethod(c, func); }, + .template, .userLambda => {}, } } @@ -2022,12 +2072,32 @@ pub fn getHostAttrName(c: *cy.Chunk, attr: *ast.Attribute) !?[]const u8 { return c.ast.nodeString(value); } +pub fn resolveHostFuncVariant(c: *cy.Chunk, func: *cy.Func) !void { + try pushFuncVariantResolveContext(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); +} + pub fn resolveHostFunc(c: *cy.Chunk, func: *cy.Func) !void { if (func.isResolved()) { return; } - const func_sig = try resolveFuncSig(c, func); - try resolveHostFunc2(c, func, func_sig); + + 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); + } } fn resolveHostFunc2(c: *cy.Chunk, func: *cy.Func, func_sig: FuncSigId) !void { @@ -2075,9 +2145,28 @@ pub fn reserveUserFunc2(c: *cy.Chunk, parent: *cy.Sym, name: []const u8, decl: * return c.reserveUserFunc(parent, name, decl, is_method, is_variant); } +pub fn resolveUserFuncVariant(c: *cy.Chunk, func: *cy.Func) !void { + try pushFuncVariantResolveContext(c, func.variant.?); + defer popResolveContext(c); + getResolveContext(c).expand_ct_inferred_params = true; + + const sig_id = try resolveFuncSig(c, func, true); + try c.resolveUserFunc(func, sig_id); +} + pub fn resolveUserFunc(c: *cy.Chunk, func: *cy.Func) !void { - const func_sig = try resolveFuncSig(c, func); - try c.resolveUserFunc(func, func_sig); + 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 { @@ -2091,14 +2180,15 @@ pub fn reserveImplicitTraitMethod(c: *cy.Chunk, parent: *cy.Sym, decl: *ast.Func } pub fn resolveImplicitTraitMethod(c: *cy.Chunk, func: *cy.Func) !void { - const func_sig = try resolveImplicitMethodSig(c, func); + const rec_param = FuncParam.initRt(func.parent.getStaticType().?); + const func_sig = try resolveImplicitMethodSig(c, func, rec_param, false); try c.resolveUserFunc(func, func_sig); } pub fn reserveImplicitMethod(c: *cy.Chunk, parent: *cy.Sym, decl: *ast.FuncDecl, is_variant: bool) !*cy.Func { - const decl_path = try ensureDeclNamePath(c, parent, decl.name); + const name = c.ast.nodeString(decl.name); if (decl.stmts.len > 0) { - const func = try c.reserveUserFunc(decl_path.parent, decl_path.name.base_name, decl, true, is_variant); + const func = try c.reserveUserFunc(parent, name, decl, true, is_variant); func.is_implicit_method = true; return func; } @@ -2106,29 +2196,101 @@ pub fn reserveImplicitMethod(c: *cy.Chunk, parent: *cy.Sym, decl: *ast.FuncDecl, // No initializer. Check if @host func. if (decl.attrs.len > 0) { if (decl.attrs[0].type == .host) { - const func = try c.reserveHostFunc(decl_path.parent, decl_path.name.base_name, decl, true, is_variant); + const func = try c.reserveHostFunc(parent, name, decl, true, is_variant); func.is_implicit_method = true; return func; } } - return c.reportErrorFmt("`{}` does not have an initializer.", &.{v(decl_path.name.name_path)}, @ptrCast(decl)); + + 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.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 = .{}, + }; + try c.resolveTemplateFunc(func, sig_id, template); +} + +pub fn resolveImplicitMethodVariant(c: *cy.Chunk, func: *cy.Func) !void { + try pushFuncVariantResolveContext(c, func.variant.?); + defer popResolveContext(c); + getResolveContext(c).expand_ct_inferred_params = true; + + const rec_param = FuncParam.initRt(func.parent.getStaticType().?); + const sig_id = try resolveImplicitMethodSig(c, func, rec_param, true); + if (func.type == .hostFunc) { + try resolveHostFunc2(c, func, sig_id); + } else { + try c.resolveUserFunc(func, sig_id); + } } -pub fn resolveImplicitMethod(c: *cy.Chunk, func: *cy.Func) !void { - const func_sig = try resolveImplicitMethodSig(c, func); +pub fn resolveImplicitMethod(c: *cy.Chunk, func: *cy.Func, has_parent_ctx: bool) !void { + try pushResolveContext(c); + defer popResolveContext(c); + getResolveContext(c).has_parent_ctx = has_parent_ctx; + + const method_rec_t = func.parent.getStaticType().?; + const rec_param = FuncParam.initRt(method_rec_t); + + const sig_id = try resolveImplicitMethodSig(c, func, rec_param, 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); + return; + } if (func.type == .hostFunc) { - try resolveHostFunc2(c, func, func_sig); + try resolveHostFunc2(c, func, sig_id); } else { - try c.resolveUserFunc(func, func_sig); + try c.resolveUserFunc(func, sig_id); } } pub fn methodDecl(c: *cy.Chunk, func: *cy.Func) !void { - if (func.sym.?.variant) |variant| { - const ctx = try pushTemplateContext(c, variant); - defer popTemplateContext(c, ctx) catch @panic("error"); + if (func.variant) |variant| { + try pushFuncVariantResolveContext(c, variant); + defer popResolveContext(c); + try methodDecl2(c, func); + return; + } + + if (func.parent.getVariant()) |parent_variant| { + try pushVariantResolveContext(c, parent_variant); + defer popResolveContext(c); try methodDecl2(c, func); } else { + try pushResolveContext(c); + defer popResolveContext(c); try methodDecl2(c, func); } } @@ -2143,18 +2305,25 @@ pub fn methodDecl2(c: *cy.Chunk, func: *cy.Func) !void { const blockId = try pushFuncProc(c, func); c.semaProcs.items[blockId].isMethodBlock = true; - try pushMethodParamVars(c, parent.getStaticType().?, func); + const sig = c.sema.getFuncSig(func.funcSigId); + const parent_t = sig.params()[0].type; + try pushMethodParamVars(c, parent_t, func); try semaStmts(c, func.decl.?.cast(.funcDecl).stmts); try popFuncBlock(c); func.emitted = true; } pub fn funcDecl(c: *cy.Chunk, func: *cy.Func) !void { - if (func.sym.?.variant) |variant| { - const ctx = try pushTemplateContext(c, variant); - defer popTemplateContext(c, ctx) catch @panic("error"); + if (func.variant) |variant| { + try pushFuncVariantResolveContext(c, variant); + defer popResolveContext(c); + + const sig = try c.sema.allocFuncSigStr(func.funcSigId, true, null); + defer c.alloc.free(sig); try funcDecl2(c, func); } else { + try pushResolveContext(c); + defer popResolveContext(c); try funcDecl2(c, func); } } @@ -2202,6 +2371,9 @@ pub fn resolveContextVar(c: *cy.Chunk, sym: *cy.sym.ContextVar) !void { return error.TODO; } + try pushResolveContext(c); + defer popResolveContext(c); + const name = sym.head.name(); const exp_var = c.compiler.context_vars.get(name) orelse { return c.reportErrorFmt("Context variable `{}` does not exist.", &.{v(sym.head.name())}, @ptrCast(sym.decl.name)); @@ -2223,6 +2395,10 @@ pub fn resolveUserVar(c: *cy.Chunk, sym: *cy.sym.UserVar) !void { if (sym.isResolved()) { return; } + + try pushResolveContext(c); + defer popResolveContext(c); + if (sym.decl.?.typed) { const typeId = try resolveTypeSpecNode(c, sym.decl.?.typeSpec); c.resolveUserVar(sym, typeId); @@ -2232,6 +2408,9 @@ pub fn resolveUserVar(c: *cy.Chunk, sym: *cy.sym.UserVar) !void { } pub fn resolveHostVar(c: *cy.Chunk, sym: *cy.sym.HostVar) !void { + try pushResolveContext(c); + defer popResolveContext(c); + const decl = sym.decl.?; const name = c.ast.getNamePathInfo(decl.name); @@ -2263,7 +2442,7 @@ pub fn resolveHostVar(c: *cy.Chunk, sym: *cy.sym.HostVar) !void { try c.resolveHostVar(sym, typeId, out); } -fn declareParam(c: *cy.Chunk, param: ?*ast.FuncParam, isSelf: bool, paramIdx: u32, declT: TypeId) !void { +fn declareParam(c: *cy.Chunk, param: ?*ast.FuncParam, isSelf: bool, paramIdx: usize, declT: TypeId) !void { var name: []const u8 = undefined; if (isSelf) { name = "self"; @@ -2427,6 +2606,9 @@ fn localDecl(c: *cy.Chunk, node: *ast.VarDecl) !void { } pub fn staticDecl(c: *cy.Chunk, sym: *Sym, node: *ast.StaticVarDecl) !void { + try sema.pushResolveContext(c); + defer sema.popResolveContext(c); + c.curInitingSym = sym; c.curInitingSymDeps.clearRetainingCapacity(); defer c.curInitingSym = null; @@ -2579,88 +2761,6 @@ fn callTypeSym(c: *cy.Chunk, preIdx: u32, sym: *Sym, symNodeId: *ast.Node, args: return c.semaCallFuncSym(preIdx, funcSym, args, ret_cstr, symNodeId); } -fn callTemplateSym(c: *cy.Chunk, loc: u32, template: *cy.sym.Template, args: []*ast.Node, ret_cstr: ReturnCstr, node: *ast.Node) !ExprResult { - if (template.kind != .func) { - return error.Unsupported; - } - - if (!template.canInferFuncTemplateArgs()) { - return c.reportError("Can not infer template params.", node); - } - if (template.infer_min_params > args.len) { - return c.reportError("Can not infer template params.", node); - } - - // First resolve args up to `min_infer_params` without a target arg type. - const start = c.typeStack.items.len; - defer c.typeStack.items.len = start; - const args_loc = try c.ir.pushEmptyArray(c.alloc, u32, args.len); - var arg_idx: u32 = 0; - while (arg_idx < template.infer_min_params) { - const arg_res = try c.semaExpr(args[arg_idx], .{}); - c.ir.setArrayItem(args_loc, u32, arg_idx, arg_res.irIdx); - try c.typeStack.append(c.alloc, arg_res.type.id); - arg_idx += 1; - } - - // Infer template params. - const targ_start = c.valueStack.items.len; - try c.valueStack.resize(c.alloc, targ_start + template.params.len); - const targs = c.valueStack.items[targ_start..]; - @memset(targs, cy.Value.Void); - defer { - c.valueStack.items.len = targ_start; - for (targs) |targ| { - c.vm.release(targ); - } - } - for (0..template.params.len) |i| { - const func_idx = template.infer_from_func_params[i]; - c.valueStack.items[targ_start + i] = try c.vm.allocType(c.typeStack.items[start+func_idx]); - } - const targ_types_start = c.typeStack.items.len; - try c.typeStack.resize(c.alloc, targ_types_start + template.params.len); - const targ_types = c.typeStack.items[targ_types_start..]; - @memset(targ_types, bt.Type); - const func_sym = (try cte.expandTemplate(c, template, targs, targ_types)).cast(.func); - const func = func_sym.first; - c.typeStack.items.len = targ_types_start; - - // The rest of the args are resolved with target arg type. - const sig = c.sema.getFuncSig(func.funcSigId); - const params = sig.params(); - while (arg_idx < args.len) { - var arg_res: ExprResult = undefined; - if (arg_idx < params.len) { - arg_res = try c.semaExprTarget(args[arg_idx], params[arg_idx]); - } else { - arg_res = try c.semaExpr(args[arg_idx], .{}); - } - c.ir.setArrayItem(args_loc, u32, arg_idx, arg_res.irIdx); - try c.typeStack.append(c.alloc, arg_res.type.id); - arg_idx += 1; - } - const arg_types = c.typeStack.items[start..]; - - // Check func sig against args. - const func_params = c.sema.getFuncSig(func.funcSigId).params(); - if (func_params.len != arg_types.len) { - return reportIncompatibleCallSig(c, func_sym, arg_types, ret_cstr, node); - } - for (arg_types, 0..) |arg_t, i| { - if (!cy.types.isTypeSymCompat(c.compiler, arg_t, func_params[i])) { - return reportIncompatibleCallSig(c, func_sym, arg_types, ret_cstr, node); - } - } - - c.ir.setExprCode(loc, .preCallFuncSym); - c.ir.setExprType2(loc, .{ .id = @intCast(func.retType), .throws = func.throws }); - c.ir.setExprData(loc, .preCallFuncSym, .{ .callFuncSym = .{ - .func = func, .numArgs = @as(u8, @intCast(args.len)), .args = args_loc, - }}); - return ExprResult.init(loc, CompactType.init(func.retType)); -} - fn callSym(c: *cy.Chunk, preIdx: u32, sym: *Sym, symNode: *ast.Node, args: []*ast.Node, ret_cstr: ReturnCstr) !ExprResult { try referenceSym(c, sym, symNode); switch (sym.type) { @@ -2711,7 +2811,8 @@ fn callSym(c: *cy.Chunk, preIdx: u32, sym: *Sym, symNode: *ast.Node, args: []*as return c.semaCallValue(preIdx, callee.irIdx, args.len, args_loc); }, .template => { - return callTemplateSym(c, preIdx, sym.cast(.template), args, ret_cstr, symNode); + const final_sym = try cte.expandTemplateOnCallArgs(c, sym.cast(.template), args, symNode); + return sema.symbol(c, final_sym, symNode, false); }, else => { // try pushCallArgs(c, node.data.callExpr.argHead, numArgs, true); @@ -2903,18 +3004,26 @@ pub fn getResolvedLocalSym(c: *cy.Chunk, name: []const u8, node: *ast.Node, dist } } - // Look in template context. - if (c.cur_template_params.size > 0) { - if (c.cur_template_params.get(name)) |param| { - if (param.getTypeId() != bt.Type) { - const param_type_name = c.sema.getTypeBaseName(param.getTypeId()); - return c.reportErrorFmt("Can not use a `{}` template param here.", &.{v(param_type_name)}, node); - } - const sym = c.sema.getTypeSym(param.asHeapObject().type.type); - if (!distinct or sym.isDistinct()) { - return sym; + // Look in ct context. + var resolve_ctx_idx = c.resolve_stack.items.len-1; + while (true) { + const ctx = c.resolve_stack.items[resolve_ctx_idx]; + if (ctx.ct_params.size > 0) { + if (ctx.ct_params.get(name)) |param| { + if (param.getTypeId() != bt.Type) { + const param_type_name = c.sema.getTypeBaseName(param.getTypeId()); + return c.reportErrorFmt("Can not use a `{}` template param here.", &.{v(param_type_name)}, node); + } + const sym = c.sema.getTypeSym(param.asHeapObject().type.type); + if (!distinct or sym.isDistinct()) { + return sym; + } } } + if (!ctx.has_parent_ctx) { + break; + } + resolve_ctx_idx -= 1; } // Look in use alls. @@ -2985,7 +3094,7 @@ fn resolveSymType(c: *cy.Chunk, expr_id: *ast.Node) !cy.TypeId { }; } -pub fn resolveSym(c: *cy.Chunk, expr: *ast.Node) !*cy.Sym { +pub fn resolveSym(c: *cy.Chunk, expr: *ast.Node) anyerror!*cy.Sym { switch (expr.type()) { .objectDecl => { // Unnamed object. @@ -3019,6 +3128,35 @@ pub fn resolveSym(c: *cy.Chunk, expr: *ast.Node) !*cy.Sym { return c.getResolvedDistinctSym(parent, name, access_expr.right, true); }, .comptimeExpr => { + const ctx = getResolveContext(c); + if (ctx.parse_ct_inferred_params) { + const ct_expr = expr.cast(.comptimeExpr); + 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 setResolveCtParam(c, param_name, ref_v); + + return c.sema.types.items[bt.CTInfer].sym; + } + + if (ctx.expand_ct_inferred_params) { + const ct_expr = expr.cast(.comptimeExpr); + 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) { + return c.reportErrorFmt("Expected `type`.", &.{}, ct_expr.child); + } + const type_id = val.asHeapObject().type.type; + return c.sema.types.items[type_id].sym; + } + if (c.in_ct_expr) { return c.reportErrorFmt("Unexpected compile-time expression.", &.{}, expr); } @@ -3030,16 +3168,17 @@ pub fn resolveSym(c: *cy.Chunk, expr: *ast.Node) !*cy.Sym { .array_expr => { const array_expr = expr.cast(.array_expr); var left = try resolveSym(c, array_expr.left); - if (left.type == .template) { - return cte.expandTemplateOnCallArgs(c, left.cast(.template), array_expr.args, expr); + if (left.type != .template) { + return c.reportErrorFmt("Unsupported array expression.", &.{}, expr); } - return c.reportErrorFmt("Unsupported array expression.", &.{}, expr); + return cte.expandTemplateOnCallArgs(c, left.cast(.template), array_expr.args, expr); }, .semaSym => { return expr.cast(.semaSym).sym; }, .callExpr => { - return try cte.expandTemplateOnCallExpr(c, expr.cast(.callExpr)); + return error.TODO; + // return try cte.expandTemplateOnCallExpr(c, expr.cast(.callExpr)); }, .expandOpt => { return try cte.expandTemplateOnCallArgs(c, c.sema.option_tmpl, &.{expr.cast(.expandOpt).param}, expr); @@ -3050,7 +3189,11 @@ pub fn resolveSym(c: *cy.Chunk, expr: *ast.Node) !*cy.Sym { } } -pub fn resolveImplicitMethodSig(c: *cy.Chunk, func: *cy.Func) !FuncSigId { +fn getResolveContext(c: *cy.Chunk) *ResolveContext { + return &c.resolve_stack.items[c.resolve_stack.items.len-1]; +} + +pub fn resolveImplicitMethodSig(c: *cy.Chunk, func: *cy.Func, rec_param: FuncParam, skip_ct_params: bool) !FuncSigId { const func_n = func.decl.?.cast(.funcDecl); // Get params, build func signature. @@ -3058,20 +3201,28 @@ pub fn resolveImplicitMethodSig(c: *cy.Chunk, func: *cy.Func) !FuncSigId { defer c.typeStack.items.len = start; // First param is always `self`. - try c.typeStack.append(c.alloc, func.parent.getStaticType().?); + try c.typeStack.append(c.alloc, @bitCast(rec_param)); for (func_n.params) |param| { const paramName = c.ast.funcParamName(param); if (std.mem.eql(u8, "self", paramName)) { return c.reportErrorFmt("`self` param is not allowed in an implicit method declaration.", &.{}, @ptrCast(param)); } - const typeId = try resolveTypeSpecNode(c, param.typeSpec); - try c.typeStack.append(c.alloc, typeId); + const type_id = try resolveTypeSpecNode(c, param.typeSpec); + + if (param.ct_param) { + if (skip_ct_params) continue; + const param_v = try c.vm.allocType(type_id); + try setResolveCtParam(c, paramName, param_v); + } + + const param_t = FuncParam.init(type_id, param.ct_param); + try c.typeStack.append(c.alloc, @bitCast(param_t)); } // Get return type. const retType = try resolveReturnTypeSpecNode(c, func_n.ret); - return c.sema.ensureFuncSig(c.typeStack.items[start..], retType); + return c.sema.ensureFuncSig(@ptrCast(c.typeStack.items[start..]), retType); } fn resolveTemplateSig(c: *cy.Chunk, params: []*ast.FuncParam, outSigId: *FuncSigId) ![]cy.sym.TemplateParam { @@ -3094,11 +3245,12 @@ fn resolveTemplateSig(c: *cy.Chunk, params: []*ast.FuncParam, outSigId: *FuncSig } const retType = bt.Type; - outSigId.* = try c.sema.ensureFuncSig(c.typeStack.items[typeStart..], retType); + outSigId.* = try c.sema.ensureFuncSig(@ptrCast(c.typeStack.items[typeStart..]), retType); return tparams; } -fn resolveFuncSig(c: *cy.Chunk, func: *cy.Func) !FuncSigId { +/// `skip_ct_params` is used when expanding a template. +fn resolveFuncSig(c: *cy.Chunk, func: *cy.Func, skip_ct_params: bool) !FuncSigId { const func_n = func.decl.?.cast(.funcDecl); // Get params, build func signature. @@ -3124,14 +3276,27 @@ fn resolveFuncSig(c: *cy.Chunk, func: *cy.Func) !FuncSigId { return c.reportError("Type specifier not allowed in `let` declaration. Declare typed functions with `func`.", @ptrCast(param)); } } - const typeId = try resolveTypeSpecNode(c, param.typeSpec); - try c.typeStack.append(c.alloc, typeId); + + const type_id = try resolveTypeSpecNode(c, param.typeSpec); + + if (param.ct_param) { + 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.ct_param); + try c.typeStack.append(c.alloc, @bitCast(param_t)); } } // Get return type. const retType = try resolveReturnTypeSpecNode(c, func_n.ret); - return c.sema.ensureFuncSig(c.typeStack.items[start..], retType); + return c.sema.ensureFuncSig(@ptrCast(c.typeStack.items[start..]), retType); } fn resolveLambdaFuncSig(c: *cy.Chunk, n: *ast.LambdaExpr) !FuncSigId { @@ -3159,7 +3324,7 @@ fn resolveLambdaFuncSig(c: *cy.Chunk, n: *ast.LambdaExpr) !FuncSigId { // Get return type. const retType = try resolveReturnTypeSpecNode(c, n.ret); - return c.sema.ensureFuncSig(c.typeStack.items[start..], retType); + return c.sema.ensureFuncSig(@ptrCast(c.typeStack.items[start..]), retType); } const DeclNamePathResult = struct { @@ -3441,20 +3606,22 @@ fn pushMethodParamVars(c: *cy.Chunk, objectT: TypeId, func: *const cy.Func) !voi const param_decls = func.decl.?.cast(.funcDecl).params; if (param_decls.len > 0) { const name = c.ast.funcParamName(param_decls[0]); + var rest: []const *ast.FuncParam = undefined; if (std.mem.eql(u8, name, "self")) { try declareParam(c, param_decls[0], false, 0, objectT); - for (params[1..], 1..) |paramT, idx| { - try declareParam(c, param_decls[idx], false, @intCast(idx), paramT); - } + rest = param_decls[1..]; } else { // Implicit `self` param. try declareParam(c, null, true, 0, objectT); - // First param. - try declareParam(c, param_decls[0], false, 1, params[1]); - - for (params[2..], 2..) |paramT, idx| { - try declareParam(c, param_decls[idx-1], false, @intCast(idx), paramT); + rest = param_decls[0..]; + } + var rt_param_idx: usize = 1; + for (rest) |param_decl| { + if (param_decl.ct_param) { + continue; } + try declareParam(c, param_decl, false, rt_param_idx, params[rt_param_idx].type); + rt_param_idx += 1; } } else { // Implicit `self` param. @@ -3465,8 +3632,14 @@ fn pushMethodParamVars(c: *cy.Chunk, objectT: TypeId, func: *const cy.Func) !voi fn appendFuncParamVars(c: *cy.Chunk, func: *const cy.Func, params: []const *ast.FuncParam) !void { if (func.numParams > 0) { const rFuncSig = c.compiler.sema.funcSigs.items[func.funcSigId]; - for (rFuncSig.params(), 0..) |paramT, idx| { - try declareParam(c, params[idx], false, @intCast(idx), paramT); + const sig_params = rFuncSig.params(); + var rt_param_idx: usize = 0; + for (params) |param| { + if (param.ct_param) { + continue; + } + try declareParam(c, param, false, rt_param_idx, sig_params[rt_param_idx].type); + rt_param_idx += 1; } } } @@ -4344,7 +4517,7 @@ pub const ChunkExt = struct { // Trait function will always match first argument. for (args, 1..) |arg, i| { - const arg_res = try c.semaExprTarget(arg, matcher.getArgTypeHint(i)); + const arg_res = try c.semaExprTarget(arg, try matcher.resolveTargetParam(c, i)); try matcher.matchArg(c, arg, i, arg_res); } try matcher.matchEnd(c, node); @@ -4365,7 +4538,7 @@ pub const ChunkExt = struct { try matcher.matchArg(c, rec, 0, rec_res); for (args, 1..) |arg, i| { - const arg_res = try c.semaExprTarget(arg, matcher.getArgTypeHint(i)); + const arg_res = try c.semaExprTarget(arg, try matcher.resolveTargetParam(c, i)); try matcher.matchArg(c, arg, i, arg_res); } try matcher.matchEnd(c, node); @@ -4385,7 +4558,7 @@ pub const ChunkExt = struct { var i: u32 = 0; while (i < args.len) : (i += 1) { const arg_id = args[i]; - const arg_res = try c.semaExprTarget(arg_id, matcher.getArgTypeHint(i + 1)); + const arg_res = try c.semaExprTarget(arg_id, try matcher.resolveTargetParam(c, i + 1)); try matcher.matchArg(c, arg_id, i + 1, arg_res); } try matcher.matchEnd(c, node); @@ -4394,6 +4567,22 @@ pub const ChunkExt = struct { return c.semaCallFuncSymResult(loc, &matcher, node); } + pub fn semaCallFuncResult(c: *cy.Chunk, loc: u32, matcher: *FuncMatcher, node: *ast.Node) !ExprResult { + _ = node; + // try referenceSym(c, @ptrCast(matcher.sym), node); + if (matcher.dyn_call) { + return error.TODO; + } else { + const func = matcher.func; + c.ir.setExprCode(loc, .preCallFuncSym); + c.ir.setExprType2(loc, .{ .id = @intCast(func.retType), .throws = func.throws }); + c.ir.setExprData(loc, .preCallFuncSym, .{ .callFuncSym = .{ + .func = func, .numArgs = @as(u8, @intCast(matcher.nargs)), .args = matcher.args_loc, + }}); + return ExprResult.init(loc, CompactType.init(func.retType)); + } + } + pub fn semaCallFuncSymResult(c: *cy.Chunk, loc: u32, matcher: *FuncMatcher, node: *ast.Node) !ExprResult { try referenceSym(c, @ptrCast(matcher.sym), node); if (matcher.dyn_call) { @@ -4453,13 +4642,29 @@ pub const ChunkExt = struct { return c.semaCallFuncSymResult(loc, &matcher, node); } + pub fn semaCallFunc(c: *cy.Chunk, loc: u32, func: *cy.Func, args: []*ast.Node, ret_cstr: ReturnCstr, node: *ast.Node) !ExprResult { + var matcher = try FuncMatcher.initSingle(c, func, args.len, ret_cstr); + defer matcher.deinit(c); + + for (args, 0..) |arg, i| { + const target_t = try matcher.resolveTargetParam(c, i); + const arg_res = try c.semaExprTarget(arg, target_t); + try matcher.matchArgSingle(c, arg, i, arg_res); + } + + try matcher.matchEndSingle(c, node); + + return c.semaCallFuncResult(loc, &matcher, node); + } + /// Match first overloaded function. pub fn semaCallFuncSym(c: *cy.Chunk, loc: u32, sym: *cy.sym.FuncSym, args: []*ast.Node, ret_cstr: ReturnCstr, node: *ast.Node) !ExprResult { var matcher = try FuncMatcher.init(c, sym, args.len, ret_cstr); defer matcher.deinit(c); for (args, 0..) |arg, i| { - const arg_res = try c.semaExprTarget(arg, matcher.getArgTypeHint(i)); + const target_t = try matcher.resolveTargetParam(c, i); + const arg_res = try c.semaExprTarget(arg, target_t); try matcher.matchArg(c, arg, i, arg_res); } @@ -4490,6 +4695,15 @@ pub const ChunkExt = struct { if (left.data.sym.type == .template) { const final_sym = try cte.expandTemplateOnCallArgs(c, left.data.sym.cast(.template), array_expr.args, node); return sema.symbol(c, final_sym, node, 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); + return ExprResult.initCustom(cy.NullId, .func, CompactType.init(bt.Void), .{ .func = func_res }); + } + } } left = try sema.symbol(c, left.data.sym, node, true); } @@ -4990,6 +5204,10 @@ pub const ChunkExt = struct { }, .lambda_multi => { const lambda = node.cast(.lambda_multi); + + try pushResolveContext(c); + defer popResolveContext(c); + const func_sig = try resolveLambdaFuncSig(c, lambda); const func = try c.addUserLambda(@ptrCast(c.sym), func_sig, lambda); _ = try pushLambdaProc(c, func); @@ -5204,6 +5422,8 @@ pub const ChunkExt = struct { const calleeRes = try c.semaExprSkipSym(node.callee); if (calleeRes.resType == .sym) { return callSym(c, preIdx, calleeRes.data.sym, node.callee, node.args, expr.getRetCstr()); + } else if (calleeRes.resType == .func) { + return c.semaCallFunc(preIdx, calleeRes.data.func, node.args, expr.getRetCstr(), @ptrCast(node)); } else { const args = try c.semaPushDynCallArgs(node.args); return c.semaCallValue(preIdx, calleeRes.irIdx, node.args.len, args); @@ -5820,7 +6040,7 @@ pub fn popFuncBlock(c: *cy.Chunk) !void { } const func = proc.func.?; - const parentType = if (func.isMethod) params[0].declT else cy.NullId; + const parentType = if (func.isMethod()) params[0].declT else cy.NullId; // Patch `pushFuncBlock` with maxLocals and param copies. c.ir.setStmtData(proc.irStart, .funcBlock, .{ @@ -5832,12 +6052,27 @@ pub fn popFuncBlock(c: *cy.Chunk) !void { }); } +pub const FuncParam = packed struct { + type: u31, + + /// Does not include ct infer types or ct ref types. + ct: bool, + + pub fn init(type_id: cy.TypeId, ct_param: bool) FuncParam { + return .{ .type = @intCast(type_id), .ct = ct_param }; + } + + pub fn initRt(type_id: cy.TypeId) FuncParam { + return .{ .type = @intCast(type_id), .ct = false }; + } +}; + pub const FuncSigId = u32; pub const FuncSig = struct { /// Last elem is the return type sym. - paramPtr: [*]const cy.TypeId, + params_ptr: [*]const FuncParam, ret: cy.TypeId, - paramLen: u16, + params_len: u16, /// If a param or the return type is not the any type. // isTyped: bool, @@ -5848,12 +6083,14 @@ pub const FuncSig = struct { /// Requires type checking if any param is not `dynamic` or `any`. reqCallTypeCheck: bool, - pub inline fn params(self: FuncSig) []const cy.TypeId { - return self.paramPtr[0..self.paramLen]; + is_template: bool, + + pub inline fn params(self: FuncSig) []const FuncParam { + return self.params_ptr[0..self.params_len]; } pub inline fn numParams(self: FuncSig) u8 { - return @intCast(self.paramLen); + return @intCast(self.params_len); } pub inline fn getRetType(self: FuncSig) cy.TypeId { @@ -5866,8 +6103,8 @@ pub const FuncSig = struct { }; const FuncSigKey = struct { - paramPtr: [*]const TypeId, - paramLen: u32, + params_ptr: [*]const FuncParam, + params_len: u32, ret: TypeId, }; @@ -5883,6 +6120,9 @@ pub const Sema = struct { types: std.ArrayListUnmanaged(cy.types.Type), + /// Maps index to the ct_ref type. + ct_ref_types: std.AutoHashMapUnmanaged(u32, cy.TypeId), + /// Resolved signatures for functions. funcSigs: std.ArrayListUnmanaged(FuncSig), funcSigMap: std.HashMapUnmanaged(FuncSigKey, FuncSigId, FuncSigKeyContext, 80), @@ -5903,6 +6143,7 @@ pub const Sema = struct { .funcSigs = .{}, .funcSigMap = .{}, .types = .{}, + .ct_ref_types = .{}, }; } @@ -5910,52 +6151,87 @@ pub const Sema = struct { for (self.funcSigs.items) |*it| { it.deinit(alloc); } + var iter = self.ct_ref_types.iterator(); + while (iter.next()) |e| { + const sym = self.types.items[e.value_ptr.*].sym.cast(.dummy_t); + alloc.destroy(sym); + } if (reset) { + self.ct_ref_types.clearRetainingCapacity(); self.types.clearRetainingCapacity(); self.funcSigs.clearRetainingCapacity(); self.funcSigMap.clearRetainingCapacity(); } else { + self.ct_ref_types.deinit(alloc); self.types.deinit(alloc); self.funcSigs.deinit(alloc); self.funcSigMap.deinit(alloc); } } + pub fn ensureCtRefType(s: *Sema, param_idx: u32) !cy.TypeId { + const res = try s.ct_ref_types.getOrPut(s.alloc, param_idx); + if (!res.found_existing) { + const new_t = try s.pushType(); + s.types.items[new_t].kind = .ct_ref; + const sym = try s.alloc.create(cy.sym.DummyType); + sym.* = .{ + .head = cy.Sym.init(.dummy_t, null, "compt-ref"), + .type = new_t, + }; + s.types.items[new_t].sym = @ptrCast(sym); + s.types.items[new_t].info.ct_ref = true; + + res.value_ptr.* = new_t; + } + return res.value_ptr.*; + } + pub fn ensureUntypedFuncSig(s: *Sema, numParams: u32) !FuncSigId { - const buf = std.mem.bytesAsSlice(cy.TypeId, &cy.tempBuf); + const buf = std.mem.bytesAsSlice(FuncParam, &cy.tempBuf); if (buf.len < numParams) return error.TooBig; - @memset(buf[0..numParams], bt.Dyn); + @memset(buf[0..numParams], FuncParam.initRt(bt.Dyn)); return try s.ensureFuncSig(buf[0..numParams], bt.Dyn); } - pub fn ensureFuncSig(s: *Sema, params: []const TypeId, ret: TypeId) !FuncSigId { + pub fn ensureFuncSigRt(s: *Sema, params: []const cy.TypeId, ret: TypeId) !FuncSigId { + return ensureFuncSig(s, @ptrCast(params), ret); + } + + pub fn ensureFuncSig(s: *Sema, params: []const FuncParam, ret: TypeId) !FuncSigId { const res = try s.funcSigMap.getOrPut(s.alloc, .{ - .paramPtr = params.ptr, - .paramLen = @intCast(params.len), + .params_ptr = params.ptr, + .params_len = @intCast(params.len), .ret = ret, }); if (res.found_existing) { return res.value_ptr.*; } else { const id: u32 = @intCast(s.funcSigs.items.len); - const new = try s.alloc.dupe(TypeId, params); + const new = try s.alloc.dupe(FuncParam, params); var reqCallTypeCheck = false; - for (params) |symId| { - if (symId != bt.Dyn and symId != bt.Any) { + var is_template = false; + for (params) |param| { + if (param.ct or s.types.items[param.type].info.ct_infer) { + is_template = true; + continue; + } + if (param.type != bt.Dyn and param.type != bt.Any) { reqCallTypeCheck = true; break; } } try s.funcSigs.append(s.alloc, .{ - .paramPtr = new.ptr, - .paramLen = @intCast(new.len), + .params_ptr = new.ptr, + .params_len = @intCast(new.len), .ret = ret, .reqCallTypeCheck = reqCallTypeCheck, + .is_template = is_template, }); res.value_ptr.* = id; res.key_ptr.* = .{ - .paramPtr = new.ptr, - .paramLen = @intCast(new.len), + .params_ptr = new.ptr, + .params_len = @intCast(new.len), .ret = ret, }; return id; @@ -5997,18 +6273,27 @@ pub const Sema = struct { return buf.items; } - pub fn allocTypesStr(s: *Sema, types: []const TypeId, from: ?*cy.Chunk) ![]const u8 { + pub fn allocTypesStr(s: *Sema, types: []const cy.TypeId, from: ?*cy.Chunk) ![]const u8 { var buf: std.ArrayListUnmanaged(u8) = .{}; defer buf.deinit(s.alloc); const w = buf.writer(s.alloc); try w.writeAll("("); - try writeFuncParams(s, w, types, from); + if (types.len > 0) { + try s.writeTypeName(w, types[0], from); + + if (types.len > 1) { + for (types[1..]) |type_id| { + try w.writeAll(", "); + try s.writeTypeName(w, type_id, from); + } + } + } try w.writeAll(")"); return buf.toOwnedSlice(s.alloc); } - pub fn allocFuncSigTypesStr(s: *Sema, params: []const TypeId, ret: TypeId, from: ?*cy.Chunk) ![]const u8 { + pub fn allocFuncSigTypesStr(s: *Sema, params: []const FuncParam, ret: TypeId, from: ?*cy.Chunk) ![]const u8 { var buf: std.ArrayListUnmanaged(u8) = .{}; defer buf.deinit(s.alloc); @@ -6017,7 +6302,7 @@ pub const Sema = struct { return buf.toOwnedSlice(s.alloc); } - pub fn allocFuncParamsStr(s: *Sema, params: []const TypeId, from: ?*cy.Chunk) ![]const u8 { + pub fn allocFuncParamsStr(s: *Sema, params: []const FuncParam, from: ?*cy.Chunk) ![]const u8 { var buf: std.ArrayListUnmanaged(u8) = .{}; defer buf.deinit(s.alloc); @@ -6047,21 +6332,21 @@ pub const Sema = struct { try writeFuncSigTypesStr(s, w, funcSig.params(), funcSig.ret, from); } - pub fn writeFuncSigTypesStr(s: *Sema, w: anytype, params: []const TypeId, ret: TypeId, from: ?*cy.Chunk) !void { + pub fn writeFuncSigTypesStr(s: *Sema, w: anytype, params: []const FuncParam, ret: TypeId, from: ?*cy.Chunk) !void { try w.writeAll("("); try writeFuncParams(s, w, params, from); try w.writeAll(") "); try s.writeTypeName(w, ret, from); } - pub fn writeFuncParams(s: *Sema, w: anytype, params: []const TypeId, from: ?*cy.Chunk) !void { + pub fn writeFuncParams(s: *Sema, w: anytype, params: []const FuncParam, from: ?*cy.Chunk) !void { if (params.len > 0) { - try s.writeTypeName(w, params[0], from); + try s.writeTypeName(w, params[0].type, from); if (params.len > 1) { for (params[1..]) |paramT| { try w.writeAll(", "); - try s.writeTypeName(w, paramT, from); + try s.writeTypeName(w, paramT.type, from); } } } @@ -6077,13 +6362,13 @@ pub const Sema = struct { pub const FuncSigKeyContext = struct { pub fn hash(_: @This(), key: FuncSigKey) u64 { var c = std.hash.Wyhash.init(0); - const bytes: [*]const u8 = @ptrCast(key.paramPtr); - c.update(bytes[0..key.paramLen*4]); + const bytes: [*]const u8 = @ptrCast(key.params_ptr); + c.update(bytes[0..key.params_len*4]); c.update(std.mem.asBytes(&key.ret)); return c.final(); } pub fn eql(_: @This(), a: FuncSigKey, b: FuncSigKey) bool { - return std.mem.eql(u32, a.paramPtr[0..a.paramLen], b.paramPtr[0..b.paramLen]); + return std.mem.eql(u32, @ptrCast(a.params_ptr[0..a.params_len]), @ptrCast(b.params_ptr[0..b.params_len])); } }; @@ -6243,16 +6528,44 @@ pub const ObjectBuilder = struct { const FuncMatcher = struct { sym: *cy.sym.FuncSym, func: *cy.Func, - func_params: []const cy.TypeId, + func_params: []const FuncParam, args_loc: u32, type_start: usize, + ct_arg_start: usize, ret_cstr: ReturnCstr, nargs: u32, + /// Used when matching template function. + rt_arg_idx: u32, + resolved_param_t: cy.TypeId, + resolved_param_idx: u32, + // Computed. has_dyn_arg: bool, dyn_call: bool, + fn initSingle(c: *cy.Chunk, func: *cy.Func, nargs: usize, ret_cstr: ReturnCstr) !FuncMatcher { + const args_loc = try c.ir.pushEmptyArray(c.alloc, u32, nargs); + const start = c.typeStack.items.len; + + const func_sig = c.sema.getFuncSig(func.funcSigId); + return FuncMatcher{ + .sym = undefined, + .args_loc = args_loc, + .type_start = start, + .ct_arg_start = c.valueStack.items.len, + .func = func, + .func_params = func_sig.params(), + .ret_cstr = ret_cstr, + .nargs = @intCast(nargs), + .has_dyn_arg = false, + .dyn_call = false, + .rt_arg_idx = 0, + .resolved_param_t = cy.NullId, + .resolved_param_idx = cy.NullId, + }; + } + fn init(c: *cy.Chunk, func_sym: *cy.sym.FuncSym, nargs: usize, ret_cstr: ReturnCstr) !FuncMatcher { const args_loc = try c.ir.pushEmptyArray(c.alloc, u32, nargs); const start = c.typeStack.items.len; @@ -6262,27 +6575,62 @@ const FuncMatcher = struct { .sym = func_sym, .args_loc = args_loc, .type_start = start, + .ct_arg_start = c.valueStack.items.len, .func = func_sym.first, .func_params = func_sig.params(), .ret_cstr = ret_cstr, .nargs = @intCast(nargs), .has_dyn_arg = false, .dyn_call = false, + .rt_arg_idx = 0, + .resolved_param_t = cy.NullId, + .resolved_param_idx = cy.NullId, }; } fn deinit(self: *FuncMatcher, c: *cy.Chunk) void { c.typeStack.items.len = self.type_start; + for (c.valueStack.items[self.ct_arg_start..]) |arg| { + c.vm.release(arg); + } + c.valueStack.items.len = self.ct_arg_start; } - fn getArgTypeHint(self: *FuncMatcher, arg_idx: usize) cy.TypeId { - if (arg_idx < self.func_params.len) { - return self.func_params[arg_idx]; + /// Returns the target param type at index. Returns `cy.NullId` if type should be inferred from arg. + fn resolveTargetParam(self: *FuncMatcher, c: *cy.Chunk, param_idx: usize) !cy.TypeId { + if (param_idx < self.func_params.len) { + if (self.func.type == .template) { + return self.resolveTemplateParamType(c, param_idx); + } + return self.func_params[param_idx].type; } else { return bt.Any; } } + fn matchEndSingle(self: *FuncMatcher, c: *cy.Chunk, node: *ast.Node) !void { + // Check for unconsumed func params. + if (self.nargs < self.func_params.len) { + const arg_types = c.typeStack.items[self.type_start..]; + if (self.nargs < self.func_params.len) { + return reportIncompatibleCallSig(c, self.sym, arg_types, self.ret_cstr, node); + } + } + + // Check return type. + if (!cy.types.isValidReturnType(c.compiler, self.func.retType, self.ret_cstr)) { + const arg_types = c.typeStack.items[self.type_start..]; + return reportIncompatibleCallSig(c, self.sym, arg_types, self.ret_cstr, node); + } + + // Becomes a dynamic call if this is an overloaded function with a dynamic arg. + self.dyn_call = self.has_dyn_arg and self.sym.numFuncs > 1; + + if (self.func.type == .template) { + return error.Unexpected; + } + } + fn matchEnd(self: *FuncMatcher, c: *cy.Chunk, node: *ast.Node) !void { // Check for unconsumed func params. if (self.nargs < self.func_params.len) { @@ -6302,6 +6650,14 @@ const FuncMatcher = struct { // Becomes a dynamic call if this is an overloaded function with a dynamic arg. self.dyn_call = self.has_dyn_arg and self.sym.numFuncs > 1; + + if (self.func.type == .template) { + // Generate function. + const ct_args = c.valueStack.items[self.ct_arg_start..]; + const new_func = try cte.expandFuncTemplate(c, self.func, ct_args); + self.func = new_func; + self.nargs = self.rt_arg_idx; + } } fn matchNextFunc(self: *FuncMatcher, c: *cy.Chunk, arg_types: []const cy.TypeId) bool { @@ -6320,7 +6676,7 @@ const FuncMatcher = struct { if (i >= self.func_params.len) { continue; } - if (!cy.types.isTypeSymCompat(c.compiler, arg_t, self.func_params[i])) { + if (!cy.types.isTypeSymCompat(c.compiler, arg_t, self.func_params[i].type)) { // Ignore `arg_t.dynamic` since dynamic args already have inserted type checks. continue; } @@ -6335,34 +6691,179 @@ const FuncMatcher = struct { return reportIncompatibleCallSig(c, self.sym, types, self.ret_cstr, arg); } - fn matchArg(self: *FuncMatcher, c: *cy.Chunk, arg: *ast.Node, arg_idx: usize, arg_res: ExprResult) !void { - if (arg_idx >= self.func_params.len) { - while (arg_idx >= self.func_params.len) { - const arg_types = c.typeStack.items[self.type_start..]; - if (!self.matchNextFunc(c, arg_types)) { - return self.reportIncompatibleArg(c, arg, arg_res); - } + fn inferCtArgs(self: *FuncMatcher, c: *cy.Chunk, arg_t: cy.TypeId, infer_t: cy.TypeId) !bool { + if (infer_t == bt.CTInfer) { + 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_e = c.sema.types.items[infer_t]; + const infer_variant = infer_e.sym.getVariant().?; + if (variant.root_template != infer_variant.root_template) { + // Parent templates don't match. + return false; + } + + for (infer_variant.args, 0..) |arg, i| { + if (!try self.inferCtArgValues(c, variant.args[i], arg)) { + return false; } } - var ct_compat = cy.types.isTypeSymCompat(c.compiler, arg_res.type.id, self.func_params[arg_idx]); - const rt_compat = arg_res.type.isDynAny(); - if (!ct_compat and !rt_compat) { - const arg_types = c.typeStack.items[self.type_start..]; - while (!ct_compat) { - // Can not be used as argument. - if (!self.matchNextFunc(c, arg_types)) { - return self.reportIncompatibleArg(c, arg, arg_res); - } + return true; + } - // Check if next func has an unconsumed param. - if (arg_idx >= self.func_params.len) { - continue; + fn inferCtArgValues(self: *FuncMatcher, c: *cy.Chunk, arg: cy.Value, infer: cy.Value) !bool { + if (self.inferCtArgValueEq(arg, infer)) { + return true; + } + + if (infer.getTypeId() != bt.Type) { + return false; + } + + const infer_t = infer.asHeapObject().type.type; + if (infer_t == bt.CTInfer) { + c.vm.retain(arg); + try c.valueStack.append(c.alloc, arg); + return true; + } + + const infer_e = c.sema.types.items[infer_t]; + 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.root_template != infer_variant.root_template) { + // Parent templates don't match. + return false; + } + + for (infer_variant.args, 0..) |infer_arg, i| { + if (!try self.inferCtArgValues(c, variant.args[i], infer_arg)) { + return false; + } + } + return true; + } + + fn inferCtArgValueEq(self: *FuncMatcher, arg: cy.Value, infer: cy.Value) bool { + _ = self; + 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 resolveTemplateParamType(self: *FuncMatcher, c: *cy.Chunk, param_idx: usize) !cy.TypeId { + if (self.resolved_param_idx == param_idx) { + return self.resolved_param_t; + } + const param_t = self.func_params[param_idx]; + + const resolved_t = try self.resolveTemplateParamType2(c, param_t.type); + // Cache result. + self.resolved_param_t = resolved_t; + self.resolved_param_idx = @intCast(param_idx); + return resolved_t; + } + + fn resolveTemplateParamType2(self: *FuncMatcher, c: *cy.Chunk, param_t: cy.TypeId) !cy.TypeId { + const type_e = c.sema.types.items[param_t]; + if (!type_e.info.ct_ref) { + if (param_t == bt.CTInfer) { + return cy.NullId; + } else { + return param_t; + } + } + + if (type_e.kind == .ct_ref) { + const param_idx = type_e.data.ct_ref.param_idx; + const ct_arg = c.valueStack.items[self.ct_arg_start + param_idx]; + if (ct_arg.getTypeId() != bt.Type) { + return error.TODO; + } + return ct_arg.asHeapObject().type.type; + } + + return error.TODO; + } + + fn matchArg2(self: *FuncMatcher, c: *cy.Chunk, arg: *ast.Node, arg_idx: usize, arg_res: ExprResult) !bool { + if (self.func.type == .template) { + if (arg_idx >= self.func_params.len) { + return false; + } + + const param = self.func_params[arg_idx]; + if (param.ct) { + // Compile-time param. + const ct_value = try cte.nodeToCtValue(c, arg); + if (ct_value.type != param.type) { + c.vm.release(ct_value.value); + return false; } + try c.valueStack.append(c.alloc, ct_value.value); + return true; + } - ct_compat = cy.types.isTypeSymCompat(c.compiler, arg_res.type.id, self.func_params[arg_idx]); + const type_e = c.sema.types.items[param.type]; + if (type_e.info.ct_infer) { + if (!try self.inferCtArgs(c, arg_res.type.id, param.type)) { + return false; + } + // Fall-through to add as rt arg. + } else { + const rparam_t = try self.resolveTemplateParamType(c, arg_idx); + const ct_compat = cy.types.isTypeSymCompat(c.compiler, arg_res.type.id, rparam_t); + if (!ct_compat) { + return false; + } } + + // Runtime arg. + try c.typeStack.append(c.alloc, arg_res.type.id); + c.ir.setArrayItem(self.args_loc, u32, self.rt_arg_idx, arg_res.irIdx); + self.rt_arg_idx += 1; + return true; + } + + if (arg_idx >= self.func_params.len) { + return false; } + const ct_compat = cy.types.isTypeSymCompat(c.compiler, arg_res.type.id, self.func_params[arg_idx].type); + const rt_compat = arg_res.type.isDynAny(); + if (!ct_compat and !rt_compat) { + return false; + } + self.has_dyn_arg = self.has_dyn_arg or rt_compat; if (ct_compat) { @@ -6374,14 +6875,30 @@ const FuncMatcher = struct { c.ir.setArrayItem(self.args_loc, u32, arg_idx, arg_res.irIdx); } else { // Insert rt arg type check if this is not an overloaded function. - const loc = try c.ir.pushExpr(.type_check, c.alloc, self.func_params[arg_idx], arg, .{ + const loc = try c.ir.pushExpr(.type_check, c.alloc, self.func_params[arg_idx].type, arg, .{ .expr = arg_res.irIdx, - .exp_type = self.func_params[arg_idx], + .exp_type = self.func_params[arg_idx].type, }); - try c.typeStack.append(c.alloc, self.func_params[arg_idx]); + try c.typeStack.append(c.alloc, self.func_params[arg_idx].type); c.ir.setArrayItem(self.args_loc, u32, arg_idx, loc); } } + return true; + } + + fn matchArg(self: *FuncMatcher, c: *cy.Chunk, arg: *ast.Node, arg_idx: usize, arg_res: ExprResult) !void { + while (!try matchArg2(self, c, arg, arg_idx, arg_res)) { + const arg_types = c.typeStack.items[self.type_start..]; + if (!self.matchNextFunc(c, arg_types)) { + return self.reportIncompatibleArg(c, arg, arg_res); + } + } + } + + fn matchArgSingle(self: *FuncMatcher, c: *cy.Chunk, arg: *ast.Node, arg_idx: usize, arg_res: ExprResult) !void { + if (!try matchArg2(self, c, arg, arg_idx, arg_res)) { + return self.reportIncompatibleArg(c, arg, arg_res); + } } }; diff --git a/src/std/os_ffi.zig b/src/std/os_ffi.zig index 48be95005..42d5814b8 100644 --- a/src/std/os_ffi.zig +++ b/src/std/os_ffi.zig @@ -830,7 +830,7 @@ pub fn ffiBindLib(vm: *cy.VM, args: [*]const Value, config: BindLibConfig) !Valu // Begin func binding generation. - var tempTypes: std.BoundedArray(cy.TypeId, 16) = .{}; + var tempTypes: std.BoundedArray(sema.FuncParam, 16) = .{}; for (ffi.cfuncs.items) |*cfunc| { // Check that symbol exists. cfunc.skip = false; @@ -845,11 +845,13 @@ pub fn ffiBindLib(vm: *cy.VM, args: [*]const Value, config: BindLibConfig) !Valu tempTypes.len = 0; if (!config.gen_table) { // Self param. - try tempTypes.append(bt.Any); + const param_t = sema.FuncParam.initRt(bt.Any); + try tempTypes.append(param_t); } for (cfunc.params) |param| { const typeId = try toCyType(param, false); - try tempTypes.append(typeId); + const param_t = sema.FuncParam.initRt(typeId); + try tempTypes.append(param_t); } const retType = try toCyType(cfunc.ret, true); @@ -955,7 +957,7 @@ pub fn ffiBindLib(vm: *cy.VM, args: [*]const Value, config: BindLibConfig) !Valu const symKey = try vm.allocAstringConcat("ptrTo", typeName); const func = cy.ptrAlignCast(cy.ZHostFuncFn, funcPtr); - const funcSigId = try vm.sema.ensureFuncSig(&.{bt.Dyn}, bt.Dyn); + const funcSigId = try vm.sema.ensureFuncSigRt(&.{ bt.Dyn }, bt.Dyn); const funcVal = try cy.heap.allocHostFunc(vm, func, 1, funcSigId, cyState, false); try table.asHeapObject().table.set(vm, symKey, funcVal); vm.release(symKey); diff --git a/src/std/test.cy b/src/std/test.cy index 42efff9ec..5bb087669 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. -@host func eq[T type](a T, b T) bool +@host func eq(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. @host func eqList(a any, b any) bool --| Returns `true` if two numbers are near each other within epsilon 1e-5. -@host func eqNear[T type](a T, b T) bool +@host func eqNear(a #T, b T) bool func fail(): throw error.AssertError diff --git a/src/sym.zig b/src/sym.zig index c3fddcc75..ad9d6c120 100644 --- a/src/sym.zig +++ b/src/sym.zig @@ -44,6 +44,8 @@ pub const SymType = enum(u8) { /// Once the intermediate sym is visited, the placeholder sym is replaced /// and it's module is copied. placeholder, + + dummy_t, }; /// Base symbol. All symbols stem from the same header. @@ -185,6 +187,9 @@ pub const Sym = extern struct { .hostVar => { alloc.destroy(self.cast(.hostVar)); }, + .dummy_t => { + alloc.destroy(self.cast(.dummy_t)); + }, .placeholder => { const placeholder = self.cast(.placeholder); if (!placeholder.resolved) { @@ -217,7 +222,9 @@ pub const Sym = extern struct { pub fn getVariant(self: *Sym) ?*Variant { switch (self.type) { + .object_t => return self.cast(.object_t).variant, .custom_t => return self.cast(.custom_t).variant, + .enum_t => return self.cast(.enum_t).variant, else => return null, } } @@ -237,6 +244,7 @@ pub const Sym = extern struct { .struct_t, .object_t, .trait_t, + .dummy_t, .enum_t => { return true; }, @@ -297,6 +305,7 @@ pub const Sym = extern struct { .context_var, .userVar, .field, + .dummy_t, .hostVar => { return null; }, @@ -351,6 +360,7 @@ pub const Sym = extern struct { return error.AmbiguousSymbol; } }, + .dummy_t, .placeholder, .field, .null, @@ -373,6 +383,7 @@ pub const Sym = extern struct { .trait_t => return self.cast(.trait_t).type, .custom_t => return self.cast(.custom_t).type, .use_alias => return self.cast(.use_alias).sym.getStaticType(), + .dummy_t => return self.cast(.dummy_t).type, .placeholder, .null, .field, @@ -414,6 +425,7 @@ pub const Sym = extern struct { .chunk, .context_var, .hostVar, + .dummy_t, .userVar => return null, } } @@ -490,10 +502,16 @@ fn SymChild(comptime symT: SymType) type { .module_alias => ModuleAlias, .chunk => Chunk, .placeholder => Placeholder, + .dummy_t => DummyType, .null => void, }; } +pub const DummyType = extern struct { + head: Sym, + type: cy.TypeId, +}; + pub const Placeholder = extern struct { head: Sym, mod: vmc.Module, @@ -540,7 +558,6 @@ pub const FuncSym = extern struct { first: *Func, last: *Func, numFuncs: u16, - variant: ?*Variant, /// Duped to perform uniqueness check without dereferencing the first ModuleFunc. firstFuncSig: cy.sema.FuncSigId, @@ -609,13 +626,6 @@ pub const Template = struct { /// Owned by root template. params: []TemplateParam, - /// For function templates, this maps to a function param that can infer this `type` param. - infer_from_func_params: []u8, - - /// The minimum number of params that can infer the template params. - /// If NullU8, then the template function can not infer the params. - infer_min_params: u8, - /// Template args to variant. Keys are not owned. variant_cache: std.HashMapUnmanaged([]const cy.Value, *Variant, VariantKeyContext, 80), @@ -637,8 +647,6 @@ pub const Template = struct { if (self.is_root) { alloc.free(self.params); } - - alloc.free(self.infer_from_func_params); } pub fn getMod(self: *Template) *cy.Module { @@ -661,10 +669,6 @@ pub const Template = struct { return parent.getMod().?.getSym(self.head.name()).?; } - pub fn canInferFuncTemplateArgs(self: *Template) bool { - return self.infer_min_params != cy.NullU8; - } - pub fn chunk(self: *const Template) *cy.Chunk { return self.head.parent.?.getMod().?.chunk; } @@ -1008,11 +1012,43 @@ pub const Chunk = extern struct { } }; +pub const FuncVariant = struct { + /// Owned args. Can be used to print params along with the template func. + args: []const cy.Value, + + template: *FuncTemplate, + + func: *Func, +}; + +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, *FuncVariant, VariantKeyContext, 80), + + pub fn deinit(self: *FuncTemplate, alloc: std.mem.Allocator) void { + var iter = self.variant_cache.iterator(); + while (iter.next()) |e| { + alloc.destroy(e.value_ptr.*); + } + self.variant_cache.deinit(alloc); + alloc.free(self.params); + } +}; + +pub const FuncTemplateParam = struct { + name: []const u8, +}; + pub const FuncType = enum { hostFunc, userFunc, userLambda, trait, + template, }; pub const Func = struct { @@ -1037,16 +1073,46 @@ pub const Func = struct { trait: struct { vtable_idx: u32, }, + template: *FuncTemplate, }, + variant: ?*FuncVariant, reqCallTypeCheck: bool, numParams: u8, - isMethod: bool, + + is_method: bool, + is_implicit_method: bool, throws: bool, /// Whether it has already emitted IR. emitted: bool, + 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 deinitRetained(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; } @@ -1055,10 +1121,18 @@ pub const Func = struct { return self.type != .userLambda; } + pub fn isMethod(self: Func) bool { + return self.is_method; + } + pub fn hasStaticInitializer(self: Func) bool { return self.type == .hostFunc; } + pub fn chunk(self: *const Func) *cy.Chunk { + return self.parent.getMod().?.chunk; + } + pub fn name(self: Func) []const u8 { if (self.type == .userLambda) { return "lambda"; @@ -1217,8 +1291,6 @@ pub const ChunkExt = struct { .is_root = is_root, .params = params, .sigId = sigId, - .infer_min_params = cy.NullU8, - .infer_from_func_params = &.{}, .variant_cache = .{}, .mod = undefined, }); @@ -1371,9 +1443,10 @@ pub const ChunkExt = struct { .sym = sym, .throws = false, .parent = parent, - .isMethod = isMethod, + .is_method = isMethod, .is_implicit_method = false, .numParams = undefined, + .variant = null, .decl = node, .next = null, .data = undefined, @@ -1389,7 +1462,6 @@ pub const ChunkExt = struct { .firstFuncSig = cy.NullId, .first = undefined, .last = undefined, - .variant = null, }); try c.syms.append(c.alloc, @ptrCast(sym)); return sym; @@ -1460,9 +1532,9 @@ pub fn writeFuncName(s: *cy.Sema, w: anytype, func: *cy.Func, config: SymFormatC try writeParentPrefix(s, w, @ptrCast(func.sym.?), config); try w.writeAll(func.name()); if (config.emit_template_args) { - if (func.sym.?.variant) |variant| { + if (func.variant) |variant| { try w.writeByte('['); - try writeLocalTemplateArgs(s, w, variant, config); + try writeLocalFuncTemplateArgs(s, w, variant, config); try w.writeByte(']'); } } @@ -1540,14 +1612,31 @@ fn writeLocalTemplateArgs(s: *cy.Sema, w: anytype, variant: *cy.sym.Variant, con } } +fn writeLocalFuncTemplateArgs(s: *cy.Sema, w: anytype, variant: *cy.sym.FuncVariant, config: SymFormatConfig) !void { + const args = variant.args; + if (args[0].getTypeId() != bt.Type) { + return error.Unsupported; + } + var sym = s.getTypeSym(args[0].asHeapObject().type.type); + try writeSymName(s, w, sym, config); + for (args[1..]) |arg| { + try w.writeByte(','); + if (arg.getTypeId() != bt.Type) { + return error.Unsupported; + } + sym = s.getTypeSym(arg.asHeapObject().type.type); + try writeSymName(s, w, sym, config); + } +} + test "sym internals" { if (builtin.mode == .ReleaseFast) { if (cy.is32Bit) { try t.eq(@sizeOf(Sym), 16); - try t.eq(@sizeOf(Func), 36); + try t.eq(@sizeOf(Func), 40); } else { try t.eq(@sizeOf(Sym), 24); - try t.eq(@sizeOf(Func), 56); + try t.eq(@sizeOf(Func), 64); } } else { if (cy.is32Bit) { @@ -1555,7 +1644,7 @@ test "sym internals" { try t.eq(@sizeOf(Func), 40); } else { try t.eq(@sizeOf(Sym), 24); - try t.eq(@sizeOf(Func), 64); + try t.eq(@sizeOf(Func), 72); } } diff --git a/src/types.zig b/src/types.zig index aae996ce2..9ddcea46f 100644 --- a/src/types.zig +++ b/src/types.zig @@ -25,6 +25,8 @@ pub const TypeKind = enum(u8) { @"struct", option, trait, + bare, + ct_ref, }; pub const TypeInfo = packed struct { @@ -33,7 +35,14 @@ pub const TypeInfo = packed struct { /// If `true`, invoke finalizer before releasing children. custom_pre: bool = false, - padding: u6 = undefined, + 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, }; pub const Type = extern struct { @@ -62,6 +71,9 @@ pub const Type = extern struct { @"struct": extern struct { numFields: u16, }, + ct_ref: extern struct { + param_idx: u32, + }, }, }; @@ -154,6 +166,7 @@ pub const BuiltinTypes = struct { pub const ExternFunc: TypeId = vmc.TYPE_EXTERN_FUNC; pub const Range: TypeId = vmc.TYPE_RANGE; pub const Table: TypeId = vmc.TYPE_TABLE; + pub const CTInfer: TypeId = vmc.TYPE_CTINFER; /// Used to indicate no type value. // pub const Undefined: TypeId = vmc.TYPE_UNDEFINED; @@ -331,22 +344,22 @@ pub fn isTypeFuncSigCompat(c: *cy.Compiler, args: []const CompactType, ret_cstr: } // First check params length. - if (args.len != target.paramLen) { + if (args.len != target.params_len) { return false; } // Check each param type. Attempt to satisfy constraints. for (target.params(), args) |cstrType, argType| { - if (isTypeSymCompat(c, argType.id, cstrType)) { + if (isTypeSymCompat(c, argType.id, cstrType.type)) { continue; } if (argType.dynamic) { - if (isTypeSymCompat(c, cstrType, argType.id)) { + if (isTypeSymCompat(c, cstrType.type, argType.id)) { // Only defer to runtime type check if arg type is a parent type of cstrType. continue; } } - log.tracev("`{s}` not compatible with param `{s}`", .{c.sema.getTypeBaseName(argType.id), c.sema.getTypeBaseName(cstrType)}); + log.tracev("`{s}` not compatible with param `{s}`", .{c.sema.getTypeBaseName(argType.id), c.sema.getTypeBaseName(cstrType.type)}); return false; } @@ -386,7 +399,7 @@ pub fn isFuncSigCompat(c: *cy.Compiler, id: sema.FuncSigId, targetId: sema.FuncS const target = c.sema.getFuncSig(targetId); // First check params length. - if (src.paramLen != target.paramLen) { + if (src.params_len != target.params_len) { return false; } @@ -514,4 +527,4 @@ fn visitTypeHasZeroInit(c: *cy.Chunk, obj: *cy.sym.ObjectType) !ZeroInitResult { } c.typeDeps.items[entryId].visited = true; return finalRes; -} +} \ No newline at end of file diff --git a/src/vm.h b/src/vm.h index 073699574..3740be022 100644 --- a/src/vm.h +++ b/src/vm.h @@ -395,12 +395,11 @@ enum { TYPE_METATYPE = 27, TYPE_RANGE = 28, TYPE_TABLE = 29, - - // TYPE_UNDEFINED = 17, + TYPE_CTINFER = 30, }; #define PrimitiveEnd 9 -#define BuiltinEnd 30 +#define BuiltinEnd 31 typedef uint8_t Inst; typedef uint64_t Value; diff --git a/src/vm.zig b/src/vm.zig index 2ad29f62d..a466b8ab7 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -1665,11 +1665,15 @@ pub const VM = struct { const symType: cy.heap.MetaTypeKind = @enumFromInt(obj.metatype.type); if (symType == .object) { const name = self.compiler.sema.getTypeBaseName(obj.metatype.type); - try std.fmt.format(w, "type: {s}", .{name}); + try std.fmt.format(w, "metatype: {s}", .{name}); } else { try w.writeAll("Unknown Symbol"); } }, + bt.Type => { + const name = self.compiler.sema.getTypeBaseName(obj.type.type); + try std.fmt.format(w, "type: {s}", .{name}); + }, else => { if (typeId == cy.NullId >> 3) { try w.writeAll("danglingObject"); @@ -2107,10 +2111,10 @@ pub fn call(vm: *VM, pc: [*]cy.Inst, framePtr: [*]Value, callee: Value, ret: u8, const args = framePtr[ret+CallArgStart..ret+CallArgStart+numArgs]; const cstrFuncSig = vm.compiler.sema.getFuncSig(obj.closure.funcSigId); for (args, 0..) |arg, i| { - const cstrType = cstrFuncSig.paramPtr[i]; + const cstrType = cstrFuncSig.params_ptr[i]; const argType = arg.getTypeId(); - if (!types.isTypeSymCompat(vm.compiler, argType, cstrType)) { - if (!@call(.never_inline, inferArg, .{vm, args, i, arg, cstrType})) { + if (!types.isTypeSymCompat(vm.compiler, argType, cstrType.type)) { + if (!@call(.never_inline, inferArg, .{vm, args, i, arg, cstrType.type})) { return panicIncompatibleLambdaSig(vm, args, obj.closure.funcSigId); } } @@ -2144,10 +2148,10 @@ pub fn call(vm: *VM, pc: [*]cy.Inst, framePtr: [*]Value, callee: Value, ret: u8, const args = framePtr[ret+CallArgStart..ret+CallArgStart+numArgs]; const cstrFuncSig = vm.compiler.sema.getFuncSig(obj.lambda.funcSigId); for (args, 0..) |arg, i| { - const cstrType = cstrFuncSig.paramPtr[i]; + const cstrType = cstrFuncSig.params_ptr[i]; const argType = arg.getTypeId(); - if (!types.isTypeSymCompat(vm.compiler, argType, cstrType)) { - if (!@call(.never_inline, inferArg, .{vm, args, i, arg, cstrType})) { + if (!types.isTypeSymCompat(vm.compiler, argType, cstrType.type)) { + if (!@call(.never_inline, inferArg, .{vm, args, i, arg, cstrType.type})) { return panicIncompatibleLambdaSig(vm, args, obj.lambda.funcSigId); } } @@ -2178,10 +2182,10 @@ pub fn call(vm: *VM, pc: [*]cy.Inst, framePtr: [*]Value, callee: Value, ret: u8, const args = framePtr[ret+CallArgStart..ret+CallArgStart+numArgs]; const cstrFuncSig = vm.compiler.sema.getFuncSig(obj.hostFunc.funcSigId); for (args, 0..) |arg, i| { - const cstrType = cstrFuncSig.paramPtr[i]; + const cstrType = cstrFuncSig.params_ptr[i]; const argType = arg.getTypeId(); - if (!types.isTypeSymCompat(vm.compiler, argType, cstrType)) { - if (!@call(.never_inline, inferArg, .{vm, args, i, arg, cstrType})) { + if (!types.isTypeSymCompat(vm.compiler, argType, cstrType.type)) { + if (!@call(.never_inline, inferArg, .{vm, args, i, arg, cstrType.type})) { return panicIncompatibleLambdaSig(vm, args, obj.hostFunc.funcSigId); } } @@ -2258,7 +2262,7 @@ fn panicIncompatibleLambdaSig(vm: *cy.VM, args: []const Value, cstrFuncSigId: se defer vm.alloc.free(cstrFuncSigStr); const argTypes = try allocValueTypeIds(vm, args); defer vm.alloc.free(argTypes); - const argsSigStr = vm.compiler.sema.allocFuncSigTypesStr(argTypes, bt.Any, null) catch return error.OutOfMemory; + const argsSigStr = vm.compiler.sema.allocTypesStr(argTypes, null) catch return error.OutOfMemory; defer vm.alloc.free(argsSigStr); return vm.panicFmt( diff --git a/test/behavior_test.zig b/test/behavior_test.zig index 3faeb71f4..919e9d442 100644 --- a/test/behavior_test.zig +++ b/test/behavior_test.zig @@ -618,7 +618,7 @@ fn case(path: []const u8) !void { fn case2(config: ?Config, path: []const u8) !void { const fpath = try std.mem.concat(t.alloc, u8, &.{ thisDir(), "/", path }); defer t.alloc.free(fpath); - const contents = try std.fs.cwd().readFileAlloc(t.alloc, fpath, 1e9); + const contents = try std.fs.cwd().readFileAllocOptions(t.alloc, fpath, 1e9, null, @alignOf(u8), 0); defer t.alloc.free(contents); var idx = std.mem.indexOf(u8, contents, "cytest:") orelse { diff --git a/test/concurrency/await.cy b/test/concurrency/await.cy index 9360a0685..9f50edccc 100644 --- a/test/concurrency/await.cy +++ b/test/concurrency/await.cy @@ -9,14 +9,14 @@ var f = Future.complete(123) test.eq(await f, 123) -- Await completion from queued task. -var r = FutureResolver[int].new() +var r = FutureResolver.new(int) queueTask(): r.complete(234) test.eq(await r.future(), 234) -- Await from function call. func foo(): - var r = FutureResolver[int].new() + var r = FutureResolver.new(int) queueTask(): r.complete(234) return await r.future() diff --git a/test/core/lists.cy b/test/core/lists.cy index 91fe321e2..bf5778590 100644 --- a/test/core/lists.cy +++ b/test/core/lists.cy @@ -199,14 +199,18 @@ t.eq(res, 36) t.eq(idxRes, 9) -- List.fill with primitive. -a = List[dyn].fill(123, 10) -t.eq(a.len(), 10) -for 0..10 -> i: - t.eq(a[i], 123) +func testFill(): + var a = List.fill(123, 10) + t.eq(a.len(), 10) + for 0..10 -> i: + t.eq(a[i], 123) +testFill() -- List.fill with object performs shallow copy. -a = List[dyn].fill([], 2) -t.eq(a.len(), 2) -t.eq(a[0] == a[1], false) +func testFill2(): + var a = List.fill([], 2) + t.eq(a.len(), 2) + t.eq(a[0] == a[1], false) +testFill2() --cytest: pass \ No newline at end of file diff --git a/test/functions/call_closure_param_panic.cy b/test/functions/call_closure_param_panic.cy index da5bebd62..f1c1eedad 100644 --- a/test/functions/call_closure_param_panic.cy +++ b/test/functions/call_closure_param_panic.cy @@ -4,7 +4,7 @@ var foo = func (a int): foo(1.0) --cytest: error ---panic: Incompatible call arguments `(float) any` +--panic: Incompatible call arguments `(float)` --to the lambda `func (int) dyn`. -- --main:4:1 main: diff --git a/test/functions/call_host_param_panic.cy b/test/functions/call_host_param_panic.cy index 51fe2ef0e..cf13a2588 100644 --- a/test/functions/call_host_param_panic.cy +++ b/test/functions/call_host_param_panic.cy @@ -2,7 +2,7 @@ var foo = isDigit foo(1.0) --cytest: error ---panic: Incompatible call arguments `(float) any` +--panic: Incompatible call arguments `(float)` --to the lambda `func (int) bool`. -- --main:2:1 main: diff --git a/test/functions/call_static_lambda_incompat_arg_panic.cy b/test/functions/call_static_lambda_incompat_arg_panic.cy index 820a21ce0..368aff2df 100644 --- a/test/functions/call_static_lambda_incompat_arg_panic.cy +++ b/test/functions/call_static_lambda_incompat_arg_panic.cy @@ -4,7 +4,7 @@ var bar = foo bar(1.0) --cytest: error ---panic: Incompatible call arguments `(float) any` +--panic: Incompatible call arguments `(float)` --to the lambda `func (int) dyn`. -- --main:4:1 main: diff --git a/test/functions/lambda_incompat_arg_panic.cy b/test/functions/lambda_incompat_arg_panic.cy index 670b4c2f5..810b80b18 100644 --- a/test/functions/lambda_incompat_arg_panic.cy +++ b/test/functions/lambda_incompat_arg_panic.cy @@ -4,7 +4,7 @@ var foo = func (a int): foo(1.0) --cytest: error ---panic: Incompatible call arguments `(float) any` +--panic: Incompatible call arguments `(float)` --to the lambda `func (int) dyn`. -- --main:4:1 main: diff --git a/test/functions/template_functions.cy b/test/functions/template_functions.cy index b06244b10..e30d5be2d 100644 --- a/test/functions/template_functions.cy +++ b/test/functions/template_functions.cy @@ -1,14 +1,23 @@ use test -func add[T type](a T, b T) T: +-- Inferred template param. +func add(a #T, b T) T: return a + b +test.eq(add(1, 2), 3) +test.eq(add(1.0, 2.0), 3.0) -- Expand template params. test.eq(add[int](1, 2), 3) test.eq(add[float](1, 2), 3.0) --- Infer template params. -test.eq(add(1, 2), 3) -test.eq(add(1.0, 2.0), 3.0) +-- Explicit template param. +func add2(#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) + +-- Expand template params. +test.eq(add2[int](1, 2), 3) +test.eq(add2[float](1, 2), 3.0) --cytest: pass \ No newline at end of file diff --git a/test/modules/core.cy b/test/modules/core.cy index 76f111797..6e9a19b56 100644 --- a/test/modules/core.cy +++ b/test/modules/core.cy @@ -38,7 +38,7 @@ type S: bar any var s = S{} var oldList = [123, s] -let newList = copy(oldList) +var newList = copy(oldList) as List[dyn] t.eq(newList == oldList, false) t.eq(newList.len(), 2) t.eq(newList[0], 123) @@ -121,7 +121,7 @@ t.eq(String(123.00000123), '123.00000123') t.eq(String(int(123)), '123') t.eq(String(error.foo), 'error.foo') t.eq(String(symbol.foo), 'symbol.foo') -t.eq(String(float), 'type: float') +t.eq(String(float), 'metatype: float') -- typeof() t.eq(typeof(true), bool) diff --git a/test/types/objects.cy b/test/types/objects.cy index 2acecd5bd..865bc1cf0 100644 --- a/test/types/objects.cy +++ b/test/types/objects.cy @@ -19,17 +19,20 @@ t.eq(n2.value2, 234) var left Node = {value: 123} t.eq(left.value, 123) --- Init and default field to int(0). -var n3 = NodeHeap{} -t.eq(n3.value, 0) - type NodeHeap: - value dyn + value List[dyn] -- Init with heap value. -n3 = NodeHeap{value: [123]} +var n3 = NodeHeap{value: [123]} t.eq(n3.value[0], 123) +type NodeHeap2: + value int + +-- Init and default field to int(0). +var n4 = NodeHeap2{} +t.eq(n4.value, 0) + -- Get field from declared static var. snode.value = 123 var f = func(): @@ -66,8 +69,8 @@ t.eq(snode.value, 234) -- Set to field with heap value. n3 = NodeHeap{value: [123]} -n3.value = 234 -t.eq(n3.value, 234) +n3.value = [] +t.eq(n3.value.len(), 0) -- Struct to string returns struct's name. n1 = Node{value: 123} @@ -97,12 +100,12 @@ type BigNode: c any d any e any -var n4 = BigNode{a: 1, b: 2, c: 3, d: 4, e: 5} -t.eq(n4.a, 1) -t.eq(n4.b, 2) -t.eq(n4.c, 3) -t.eq(n4.d, 4) -t.eq(n4.e, 5) +var n5 = BigNode{a: 1, b: 2, c: 3, d: 4, e: 5} +t.eq(n5.a, 1) +t.eq(n5.b, 2) +t.eq(n5.c, 3) +t.eq(n5.d, 4) +t.eq(n5.e, 5) -- Multiple structs with the same field names but different offsets. type Node1: @@ -127,7 +130,7 @@ t.eq(nc.b, 6) -- Using Object sym as a value. var sym = Node t.eq(typeof(sym), metatype) -t.eq(String(sym), 'type: Node') +t.eq(String(sym), 'metatype: Node') -- Dynamic variable. let val = t.erase(Node{value: 123})