From 9daf72539c78ad1f4e42de7e54a86d258d188e3d Mon Sep 17 00:00:00 2001 From: dave caruso Date: Mon, 16 Sep 2024 16:53:43 -0400 Subject: [PATCH] feat: inferred return type --- CHANGELOG.md | 1 + src/Cli.zig | 4 +- src/core/Air.zig | 5 +- src/core/Comp.zig | 61 +++++++++------ src/core/InternPool.zig | 68 +++++++++-------- src/core/Report.zig | 16 +++- src/core/Sema.zig | 116 ++++++++++++++++++++++------- src/core/array.zig | 67 +++++++++-------- src/core/js/Js.zig | 2 +- src/main.zig | 5 +- src/manual/generate-man.zig | 2 +- test/behavior/arithmetic | 4 +- test/behavior/array | 2 +- test/behavior/inferred_return_type | 10 +++ test/behavior/tuple | 3 +- 15 files changed, 236 insertions(+), 130 deletions(-) create mode 100644 test/behavior/inferred_return_type diff --git a/CHANGELOG.md b/CHANGELOG.md index fe03f9e3005bc6af1baf5e863e57e42dcd27adf1..4f6700207460ac168a5fd0b303989fc0b4357681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - feat: allow instantiating `@extern` at compile time - feat: implement tuples - feat: implement multiline string literals +- feat: implement inferred return type - fix: crash when printing unused struct declaration error - fix: provide array result type to sema - fix: crash when analyzing an empty array without a result type diff --git a/src/Cli.zig b/src/Cli.zig index 6bd42b813f5ed6a81c5051d3d44487241a4a22bf..7353c2b592e90877fce1c647932410696b2f076a 100644 --- a/src/Cli.zig +++ b/src/Cli.zig @@ -268,7 +268,6 @@ pub fn parse( args, stderr.get(), stderr.tty, - true, ) catch |e| switch (e) { error.InvalidArguments => { stderr.buffered_writer.flush() catch {}; @@ -287,9 +286,8 @@ pub fn parseWithErrorWriter( args: []const []const u8, err: std.io.AnyWriter, tty: std.io.tty.Config, - comptime exit_on_error: bool, ) ParseError!p.Parsed() { - _ = exit_on_error; // autofix + @setEvalBranchQuota(std.math.maxInt(u32)); var out: p.Parsed() = .{}; var had_errors = false; var positionals_counted: usize = 0; diff --git a/src/core/Air.zig b/src/core/Air.zig index 35f72e7cbbeb7e7802307d9935ba5488259f9c48..50c3119f6789dbecdff0f15b66e333fef6ee981a 100644 --- a/src/core/Air.zig +++ b/src/core/Air.zig @@ -388,9 +388,8 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In .call => { const base_type = air.typeOf(air.instData(.call, index).base, ip); if (base_type == .@"opaque") return .@"opaque"; - return base_type.toFunctionPrototype(ip).?.return_type orelse - // unreachable // return type was not resolved - @panic("TODO: resolve return type"); + return base_type.toUnresolvedFunctionPrototype(ip).?.return_type orelse + unreachable; // return type was not resolved }, // Field accesses on structs are special-cased by storing the field diff --git a/src/core/Comp.zig b/src/core/Comp.zig index 7a40ce21f295b5a562d9a47b0e10562a591ac7a3..d133277d7b4b5b8510c9427765b7d0613483737a 100644 --- a/src/core/Comp.zig +++ b/src/core/Comp.zig @@ -119,8 +119,16 @@ pub fn enqueueEntryModule(comp: *Comp, file: *File) !Decl.Index { const ns = try comp.indexTegModule(file, .main_module_inst); const main_str = try comp.intern_pool.getOrPutString(comp.gpa, "main", .no_embedded_nulls); - const decl_index = comp.intern_pool.namespacePtr(ns).lookupDirect(comp, main_str) orelse - @panic("missing 'fn main' in root module"); + const decl_index = comp.intern_pool.namespacePtr(ns).lookupDirect(comp, main_str) orelse { + // TODO: add a helper function into Report to render a single error + var rb = Report.Builder.init(comp.gpa); + defer rb.deinit(); + try rb.err(.no_main_func, .{}); + const r: Report = try rb.finalize(); + defer r.deinit(comp.gpa); + try r.dump(.{ .comp = .{ .comp = comp, .file = file } }, comp.out); + return error.TransformFail; + }; try comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .decl = decl_index }) } }); @@ -136,7 +144,14 @@ pub fn runAllJobs(comp: *Comp) !void { } } - assert(comp.ongoing_analysis.size == 0); + if (core.assertFailed(comp.ongoing_analysis.size == 0)) { + std.debug.print("Unresumed AnalysisUnits:\n", .{}); + var it = comp.ongoing_analysis.keyIterator(); + while (it.next()) |au| { + std.debug.print("- {}\n", .{au.dump(&comp.intern_pool)}); + } + std.debug.panic("runAllJobs returned with {d} remaining ongoing_analysis", .{comp.ongoing_analysis.size}); + } } const AnalysisJobError = Allocator.Error || error{ AnalysisFail, TooManyValues }; @@ -166,10 +181,17 @@ fn runAnalysisJob(comp: *Comp, unit: InternPool.AnalysisUnit, first_blocked_sema errdefer _ = sema.deinit(.free_all); gop.value_ptr.* = sema; + switch (unit.unwrap()) { + .decl => decl.analysis = .in_progress, + .func => |v| { + v.toKey(&comp.intern_pool).func.analysis.set(&comp.intern_pool, .in_progress); + }, + } + if (first_blocked_sema) |blocked_sema| try sema.blocked_semas.append(comp.gpa, blocked_sema); - } else { - assert(first_blocked_sema == null); + } else if (first_blocked_sema) |blocked_sema| { + try gop.value_ptr.*.blocked_semas.append(comp.gpa, blocked_sema); } const sema: *Sema = gop.value_ptr.*; @@ -194,6 +216,15 @@ fn runAnalysisJob(comp: *Comp, unit: InternPool.AnalysisUnit, first_blocked_sema }, }; + switch (unit.unwrap()) { + .decl => |index| { + comp.intern_pool.declPtr(index).analysis = .complete; + }, + .func => |v| { + v.toKey(&comp.intern_pool).func.analysis.set(&comp.intern_pool, .complete); + }, + } + comp.ongoing_analysis.removeByPtr(gop.key_ptr); try comp.jobs.ensureUnusedCapacity(sema.blocked_semas.items.len); for (sema.blocked_semas.items) |other_sema| { @@ -453,29 +484,13 @@ pub const Decl = struct { /// the function type is analyzed with this set to `in_progress`; however, the semantic /// analysis of the function body is performed with this value set to `success`. Functions /// have their own analysis status field. - analysis: enum { - /// This Decl corresponds to an AST Node that has not been referenced yet, and therefore - /// because of Zig's lazy declaration analysis, it will remain unanalyzed until referenced. - unreferenced, - in_progress, - waiting, - complete, - }, + analysis: InternPool.AnalysisState, pub fn value(decl: *const Decl) InternPool.Value { assert(decl.analysis == .complete); return decl.raw_value orelse unreachable; } - pub fn @"type"(decl: *const Decl, ip: *const InternPool) InternPool.Type { - return switch (decl.analysis) { - .unreferenced => unreachable, - .in_progress => unreachable, - .in_progress_prototype_available => decl.raw_value.?.toTypeUnsafe(ip), - .complete => decl.raw_value.?.typeOf(ip), - }; - } - pub fn fmtJson(decl: *const Decl, comp: *const Comp) JsonFormatter { return .{ .decl = decl, .comp = comp }; } @@ -516,8 +531,6 @@ pub const Decl = struct { pub const Index = InternPool.DeclIndex; }; -pub const Func = struct {}; - /// A namespace is a container for declarations. Currently the only form of /// namespace that exists is file-scope. In the future, the `module` keyword /// will create nested namespaces, and it is probable that `struct` and other diff --git a/src/core/InternPool.zig b/src/core/InternPool.zig index a52dd2e72de5d1981a818216355cad6d8bc8846b..9001b9a78d2fc4b917124b600606a8e32e1c72c8 100644 --- a/src/core/InternPool.zig +++ b/src/core/InternPool.zig @@ -73,6 +73,9 @@ src_hash_deps: std.AutoArrayHashMapUnmanaged(TrackedInst.Index, DepEntry.Index) /// Dependencies on the value of a Decl. /// Value is index into `dep_entries` of the first dependency on this Decl value. decl_val_deps: std.AutoArrayHashMapUnmanaged(DeclIndex, DepEntry.Index) = .{}, +/// Dependencies on the value of a Decl. +/// Value is index into `dep_entries` of the first dependency on this Decl value. +resolved_func_deps: std.AutoArrayHashMapUnmanaged(Value, DepEntry.Index) = .{}, /// Given a `Depender`, points to an entry in `dep_entries` whose `depender` /// matches. The `next_dependee` field can be used to iterate all such entries @@ -176,6 +179,8 @@ pub const Tag = enum(u8) { owner_decl: DeclIndex, ty: Type, teg_body_inst: TrackedInst.Index, + + pub const ty_index = 2; }; /// Trailing: @@ -458,7 +463,7 @@ pub const Type = enum(u32) { return @enumFromInt(@intFromEnum(ty.value().valueTag(ip))); } - pub fn toFunctionPrototype(ty: Type, ip: *const InternPool) ?Key.FunctionType { + pub fn toUnresolvedFunctionPrototype(ty: Type, ip: *const InternPool) ?Key.FunctionType { if (ty.tag(ip) != .function_type) return null; return ip.valueToKey(ty.value()).function_type; } @@ -571,7 +576,7 @@ pub const Type = enum(u32) { switch (ty.tag(ip)) { .simple_type => unreachable, // all simple types are already printed .function_type => { - const function_type = ty.toFunctionPrototype(ip).?; + const function_type = ty.toUnresolvedFunctionPrototype(ip).?; try writer.writeAll("fn("); for (function_type.param_types, 0..) |param, i| { if (i != 0) try writer.writeAll(", "); @@ -672,30 +677,8 @@ pub const static_keys = [_]Key{ /// State that is mutable during semantic analysis. This data is not used for /// equality or hashing. pub const FuncAnalysis = packed struct(u32) { - state: State, - - pub const State = enum(u32) { - none, - // /// This function has not yet undergone analysis, because we have not - // /// seen a potential runtime call. It may be analyzed in future. none, - // /// Analysis for this function has been queued, but not yet completed. - // queued, - // /// This function intentionally only has ZIR generated because it is marked - // /// inline, which means no runtime version of the function will be generated. - // inline_only, - // in_progress, - // /// There will be a corresponding ErrorMsg in Module.failed_decls - // sema_failure, - // /// This function might be OK but it depends on another Decl which did not - // /// successfully complete semantic analysis. - // dependency_failure, - // /// There will be a corresponding ErrorMsg in Module.failed_decls. - // /// Indicates that semantic analysis succeeded, but code generation for - // /// this function failed. - // codegen_failure, - // /// Semantic analysis and code generation of this function succeeded. - // success, - }; + state: AnalysisState, + _: u30 = 0, }; /// An index into `string_bytes`. @@ -977,6 +960,11 @@ pub const Key = union(enum) { /// the resolved return type. return_type: ?Type, + pub const Resolved = struct { + param_types: []Type, + return_type: Type, + }; + pub fn hash(self: FunctionType, hasher: *Hash) void { const t = tracer.trace(@src()); defer t.end(); @@ -1066,12 +1054,19 @@ pub const Key = union(enum) { ty: Type, owner_decl: DeclIndex, teg_body_inst: TrackedInst.Index, + analysis: ExtraIndex(AnalysisState), pub fn hash(self: Func, hasher: *Hash) void { std.hash.autoHash(hasher, self.ty); std.hash.autoHash(hasher, self.owner_decl); std.hash.autoHash(hasher, self.teg_body_inst); } + + /// This should only be used to convert an inferred return type into a reified return type. + pub fn updateType(self: Func, ip: *InternPool, new_type: Type) void { + assert(new_type.tag(ip) == .function_type); + ip.extra.set(Type, @intFromEnum(self.analysis) + Tag.FuncDecl.ty_index, new_type); + } }; pub fn hash32(key: Key, ip: *const InternPool) u32 { @@ -1260,9 +1255,21 @@ pub const AnalysisUnit = packed struct(u32) { } }; +/// Every AU has a state. +/// For Decls, state is stored on the decl +/// For Functions, state is stored on the Key.Func +pub const AnalysisState = enum(u2) { + /// This Unit corresponds to an AST Node that has not been referenced yet, and therefore + /// because of Zig's lazy declaration analysis, it will remain unanalyzed until referenced. + unreferenced, + in_progress, + complete, +}; + pub const Dependee = union(enum) { src_hash: TrackedInst.Index, decl_val: DeclIndex, + resolved_func: Value, }; pub const DepEntry = extern struct { @@ -1393,11 +1400,13 @@ pub fn valueToKey(ip: *const InternPool, index: Value) Key { }, .string => .{ .string = data.decode(.string, item.tag).get(ip).toSlice(ip) }, .func => { - const func = data.decode(.func, item.tag).get(ip); + const base_index = data.decode(.func, item.tag); + const func = base_index.get(ip); return .{ .func = .{ .ty = func.ty, .owner_decl = func.owner_decl, .teg_body_inst = func.teg_body_inst, + .analysis = @enumFromInt(@intFromEnum(base_index)), } }; }, .function_type => .{ @@ -1497,9 +1506,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { ), .func => |f| brk: { const extra = try ip.extra.append(gpa, Tag.FuncDecl, .{ - .analysis = .{ - .state = .none, - }, + .analysis = .{ .state = .unreferenced }, .owner_decl = f.owner_decl, .ty = f.ty, .teg_body_inst = f.teg_body_inst, @@ -1687,6 +1694,7 @@ pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalysisUnit, de const gop = try switch (tag) { .src_hash => ip.src_hash_deps, .decl_val => ip.decl_val_deps, + .resolved_func => ip.resolved_func_deps, }.getOrPut(gpa, dependee_payload); if (gop.found_existing and ip.dep_entries.items[@intFromEnum(gop.value_ptr.*)].depender == .none) { diff --git a/src/core/Report.zig b/src/core/Report.zig index 58e59120d6ad9c8fd48cc6b270271b6c01dfc523..014f534bbd9cd9075db29f53366b24aacf76f54d 100644 --- a/src/core/Report.zig +++ b/src/core/Report.zig @@ -56,14 +56,20 @@ pub const Err = union(enum) { src: LazyNode, fn build(data: @This(), ctx: *MsgContext) !void { - try ctx.msgNode(try data.src.get(ctx), "Value must be known at compile-time", .{}); + try ctx.msgNode(try data.src.get(ctx), "value must be known at compile-time", .{}); } }, cannot_be_comptime: struct { src: LazyNode, fn build(data: @This(), ctx: *MsgContext) !void { - try ctx.msgNode(try data.src.get(ctx), "Operation cannot be performed at compile-time (TODO: make this error more specific)", .{}); + try ctx.msgNode(try data.src.get(ctx), "operation cannot be performed at compile-time (TODO: make this error more specific)", .{}); + } + }, + no_main_func: struct { + fn build(_: @This(), ctx: *MsgContext) !void { + // TODO: variant that includes file, but no location + try ctx.msgToken(0, "missing top-level function \"main\"", .{}); } }, unknown_identifier: struct { @@ -154,7 +160,7 @@ pub const Err = union(enum) { fn build(data: @This(), ctx: *MsgContext) !void { try ctx.msgNode( data.node, - "Cannot infer struct initialization type", + "cannot infer struct initialization type", .{}, ); } @@ -1055,6 +1061,10 @@ pub const MsgContext = struct { components: ArrayListUnmanaged(Component), pub fn msg(m: *MsgContext, highlight: Highlight, comptime fmt: []const u8, args: anytype) !void { + comptime if (!std.mem.startsWith(u8, fmt, "TODO")) switch (fmt[0]) { + 'A'...'Z' => @compileError("template invalid: " ++ fmt), // style guide: lowercase + else => {}, + }; var arr: std.ArrayListUnmanaged(u8) = .{}; try std.fmt.format(arr.writer(m.arena), fmt, args); try m.components.append(m.arena, .{ diff --git a/src/core/Sema.zig b/src/core/Sema.zig index 8a578ffdd2a2d1cb68474ac078a1d4f9081e0c7b..ad27c853f23ce867985ba02972d1dcfa6f21c870 100644 --- a/src/core/Sema.zig +++ b/src/core/Sema.zig @@ -419,22 +419,36 @@ pub fn run(s: *Sema) Error!void { switch (s.owner.unwrap()) { .decl => |decl_index| { const decl = s.ip.declPtr(decl_index); - decl.raw_value = result; + decl.raw_value = result.value() orelse unreachable; decl.analysis = .complete; }, .func => |value| { + const type_of_result = s.typeOfRef(result); const air = try s.func.?.finalize(s.gpa); try s.comp.jobs.writeItem(.{ .codegen_func = .{ .value = value, .air = air, } }); + + // If needed, update the function with its resolved return type + const ty = value.typeOf(s.ip); + const proto = ty.toUnresolvedFunctionPrototype(s.ip) orelse + unreachable; // must be a function type + + if (proto.return_type == null) { + const new_type = try s.makeType(.{ .function_type = .{ + .param_types = proto.param_types, + .return_type = type_of_result, + } }); + value.toKey(s.ip).func.updateType(s.ip, new_type); + } }, } return; }; } -pub fn step(s: *Sema) Error!?Value { +pub fn step(s: *Sema) Error!?Air.Inst.Ref { s.instruction_step_lock.lock(); // Semantic analysis must not be recursive defer s.instruction_step_lock.unlock(); @@ -463,10 +477,7 @@ pub fn step(s: *Sema) Error!?Value { // the instruction pointer has been moved already assert(s.frame.currentIndex() != start_index); // control flow loop }, - .stop => return switch (s.owner.kind) { - .decl => s.inst_map.get(s.frame.block_index).decode().value, - .func => .void, - }, + .stop => return s.inst_map.get(s.frame.block_index), }, } }, @@ -527,7 +538,7 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) const param: Teg.DataNode = s.frame.currentData(.param); assert(s.frame.mode == .run_time); // never emitted in a comptime block assert(s.owner.kind == .func); // only emitted in function body - const param_types = s.owner.unwrap().func.toKey(s.ip).func.ty.toFunctionPrototype(s.ip).?.param_types; + const param_types = s.owner.unwrap().func.toKey(s.ip).func.ty.toUnresolvedFunctionPrototype(s.ip).?.param_types; return s.addRef(.arg, .{ .type = param_types[param.data], .arg_number = param.data, @@ -724,7 +735,7 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) const scratch = try s.arena().alloc(Air.Inst.Ref, call.args.len); defer s.arena().free(scratch); if (base_type != .@"opaque") { - const proto = base_type.toFunctionPrototype(s.ip) orelse { + const proto = try s.resolveFunctionPrototype(base, base_type) orelse { return s.fail(.call_non_function, .{ .call = s.lazyNode(.operand), .ty = base_type, @@ -981,21 +992,29 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) const mark = core.U32Stack.Mark{ .stack = &s.scratch, .index = s.frame.mark_index }; if (s.frame_stack.items.len == 0) { - s.inst_map.add(s.frame.block_index, operand); - if (s.func) |*func| { + const proto = s.owner.unwrap().func.typeOf(s.ip).toUnresolvedFunctionPrototype(s.ip) orelse + unreachable; // must be a function + const coerced = if (proto.return_type) |return_coercion| + try s.coerceRef(s.lazyNode(.operand), operand, return_coercion) + else + operand; + s.inst_map.add(s.frame.block_index, coerced); + const indexes = mark.slice(u32); try func.extra.list.ensureUnusedCapacity(s.gpa, indexes.len + 1); const start = func.extra.list.items.len; func.extra.list.appendSliceAssumeCapacity(indexes); func.extra.list.appendAssumeCapacity(@intFromEnum(try s.addIndexUntracked(.@"break", .{ .block = .main_block, - .value = operand, + .value = coerced, }))); func.instructions.items(.data)[0] = .encode(.block, .{ .start = @intCast(start), .len = @intCast(indexes.len + 1), }); + } else { + s.inst_map.add(s.frame.block_index, operand); } return .stop; @@ -1420,7 +1439,7 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag) const operand = s.getRef(result.call_base, .no_mark); const ty = s.typeOfRef(operand); - const func: InternPool.Key.FunctionType = ty.toFunctionPrototype(s.ip) orelse + const func: InternPool.Key.FunctionType = ty.toUnresolvedFunctionPrototype(s.ip) orelse return Value.opaque_type.toAir(); if (result.argument >= func.param_types.len) @@ -1837,11 +1856,18 @@ fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst .type = ty, }), .@"opaque" => @panic("TODO: represent comptime-known opaques better"), - _ => if (!ty.hasPossibleRepresentation(s.ip)) { - return s.fail(.runtime_incompatible_type, .{ - .src = src, - .type = ty, - }); + _ => { + if (!ty.hasPossibleRepresentation(s.ip)) { + return s.fail(.runtime_incompatible_type, .{ + .src = src, + .type = ty, + }); + } + + // When a function is used as a runtime operand, it needs to + // have a resolved return type, so that calling it later is + // able to know what it returns. + _ = try s.resolveFunctionPrototype(ref, ty); }, } }, @@ -1959,23 +1985,24 @@ fn lookupDecl(s: *Sema, name: NullTerminatedString) InternPool.DeclIndex { } } -fn getDecl(sema: *Sema, decl_index: Decl.Index) !Value { - if (sema.owner.kind == .decl) { - if (decl_index == sema.owner.unwrap().decl) { - return sema.ip.declPtr(decl_index).value(); +fn getDecl(s: *Sema, decl_index: Decl.Index) !Value { + if (s.owner.kind == .decl) { + if (decl_index == s.owner.unwrap().decl) { + return s.ip.declPtr(decl_index).value(); } } - const decl_data = sema.ip.declPtr(decl_index); - try sema.declareDependency(.{ .decl_val = decl_index }); + const decl_data = s.ip.declPtr(decl_index); + try s.declareDependency(.{ .decl_val = decl_index }); const au: AnalysisUnit = .wrap(.{ .decl = decl_index }); switch (decl_data.analysis) { .unreferenced => { - try sema.comp.jobs.writeItem(.{ .analysis = .{ .au = au, .first_blocked_sema = sema } }); + try s.comp.jobs.writeItem(.{ .analysis = .{ .au = au, .first_blocked_sema = s } }); return error.Interrupt; }, - .waiting, .in_progress => { - const other_sema = sema.comp.ongoing_analysis.get(au) orelse unreachable; - try other_sema.blocked_semas.append(sema.gpa, sema); + .in_progress => { + const other_sema = s.comp.ongoing_analysis.get(au) orelse + unreachable; // analysis stopped but did not update state + try other_sema.blocked_semas.append(s.gpa, s); return error.Interrupt; }, .complete => { @@ -1984,6 +2011,39 @@ fn getDecl(sema: *Sema, decl_index: Decl.Index) !Value { } } +/// Interrupts if there is an unresolved inferred return type. +fn resolveFunctionPrototype(s: *Sema, ref: Air.Inst.Ref, ty: Type) !?InternPool.Key.FunctionType.Resolved { + const proto = ty.toUnresolvedFunctionPrototype(s.ip) orelse + return null; // not a function prototype + + if (proto.return_type) |return_type| + return .{ + .param_types = proto.param_types, + .return_type = return_type, + }; + + const value = ref.value() orelse + unreachable; // cast functions cannot have inferred return types. + + try s.declareDependency(.{ .resolved_func = value }); + + const func = value.toKey(s.ip).func; + const au: AnalysisUnit = .wrap(.{ .func = value }); + switch (func.analysis.get(s.ip)) { + .unreferenced => { + try s.comp.jobs.writeItem(.{ .analysis = .{ .au = au, .first_blocked_sema = s } }); + return error.Interrupt; + }, + .in_progress => { + const other_sema = s.comp.ongoing_analysis.get(au) orelse + unreachable; // analysis stopped but did not update state + try other_sema.blocked_semas.append(s.gpa, s); + return error.Interrupt; + }, + .complete => unreachable, // analyzed but did not set return type + } +} + fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void { try sema.ip.addDependency(sema.gpa, sema.owner, dependee); } @@ -2122,11 +2182,13 @@ fn postVisitFunctionPrototype(s: *Sema, reserve: FunctionReserve, return_type: ? .return_type = return_type, }, }); + // TODO: make a dedicated function to insert a new function const func_value = try s.makeValue(.{ .func = .{ .ty = func_type, .owner_decl = s.techDebtDeclIndex(), // TODO: ooh, this is a fun one! .teg_body_inst = tracked_inst, + .analysis = undefined, // not used when appending }, }); diff --git a/src/core/array.zig b/src/core/array.zig index 5de4c40579e8133e4b9addaf101670c02a0540f4..2afb7fd636bb3a6a212280b23aaaf5257722b0bb 100644 --- a/src/core/array.zig +++ b/src/core/array.zig @@ -2,40 +2,40 @@ const extra = @This(); pub const Mutability = enum { @"const", @"var" }; -fn setAsU32(items: []u32, comptime T: type, index: usize, payload: T) void { - const fields = std.meta.fields(@TypeOf(payload)); - var i = index; - inline for (fields) |field| { - const value = @field(payload, field.name); - switch (field.type) { - u32 => items[i] = value, - i32 => items[i] = @bitCast(value), +fn setAsU32(items: []u32, comptime T: type, i: usize, payload: T) void { + switch (T) { + u32 => items[i] = payload, + i32 => items[i] = @bitCast(payload), - [16]u8 => { - const u32s: [4]u32 = @bitCast(value); - items[i] = u32s[0]; - items[i + 1] = u32s[1]; - items[i + 2] = u32s[2]; - items[i + 3] = u32s[3]; - }, + [16]u8 => { + const u32s: [4]u32 = @bitCast(payload); + items[i] = u32s[0]; + items[i + 1] = u32s[1]; + items[i + 2] = u32s[2]; + items[i + 3] = u32s[3]; + }, - else => brk: { - const info = @typeInfo(field.type); - if (info == .@"enum") { - items[i] = @intFromEnum(value); - break :brk; - } + else => brk: { + const info = @typeInfo(T); + if (info == .@"enum") { + items[i] = @intFromEnum(payload); + break :brk; + } - const struct_data = info.@"struct"; - if (struct_data.layout == .@"packed") { - items[i] = @bitCast(value); - break :brk; - } + const struct_data = info.@"struct"; + if (struct_data.layout == .@"packed") { + items[i] = @bitCast(payload); + break :brk; + } - @call(.always_inline, setAsU32, .{ items, field.type, i, value }); - }, - } - i += extraSizeOf(field.type); + const fields = std.meta.fields(@TypeOf(payload)); + comptime var idx: usize = 0; + inline for (fields) |field| { + const value = @field(payload, field.name); + @call(.always_inline, setAsU32, .{ items, field.type, i + idx, value }); + idx += comptime extraSizeOf(field.type); + } + }, } } @@ -116,11 +116,14 @@ fn ExtraWrap(comptime kind: StoreKind) type { return enum(u32) { _, - // Reads the data from `store.extra` and returns it. pub fn get(index: @This(), owner: anytype) T { return kind.resolveStoreConst(owner).get(T, @intFromEnum(index)); } + pub fn set(index: @This(), owner: anytype, payload: T) void { + return kind.resolveStoreMut(owner).set(T, @intFromEnum(index), payload); + } + /// Returns data with a trailing reader to read data stored immediately after /// the main data structure. What is stored should be documented on `T` pub fn getTrail(index: @This(), owner: anytype) struct { @@ -196,7 +199,7 @@ pub fn EncodedData(comptime Tag: type, backing_type: type) type { } /// The number of 'u32's this type consumes in `ip.extra`. -pub fn extraSizeOf(comptime T: type) usize { +pub inline fn extraSizeOf(comptime T: type) usize { return switch (T) { u32, i32, diff --git a/src/core/js/Js.zig b/src/core/js/Js.zig index 71c01f168567a2b5d06203c1fd067d0b2a898666..a013a700b55b76e93400ba86ccbc012582d847d5 100644 --- a/src/core/js/Js.zig +++ b/src/core/js/Js.zig @@ -32,7 +32,7 @@ pub fn handleFunctionAir( try ng.build(); const tree = try ng.finalize(); - try backend.codegen_map.put(comp.gpa, value, tree); + try backend.codegen_map.putNoClobber(comp.gpa, value, tree); return 0; } diff --git a/src/main.zig b/src/main.zig index 450942749cb04907ec12150993845b683c812544..5311b43e52c814f5079d392495488fca9408db44 100644 --- a/src/main.zig +++ b/src/main.zig @@ -324,7 +324,10 @@ fn cmdBuildOrRun(ctx: Context, comptime mode: enum { build, run }) !void { try teg.dumpToWriter(stderr, null); } - const entry_point = try comp.enqueueEntryModule(main_file); + const entry_point = comp.enqueueEntryModule(main_file) catch |err| switch (err) { + error.TransformFail => std.process.exit(1), + else => |e| return e, + }; comp.runAllJobs() catch |err| switch (err) { error.AnalysisFail => { // error is already printed diff --git a/src/manual/generate-man.zig b/src/manual/generate-man.zig index 73cfa40f8bf17cf81ce53007267ef87360e0d54e..81ae60d2318b5dcebdb1b7ed708b8f71d28b1e0e 100644 --- a/src/manual/generate-man.zig +++ b/src/manual/generate-man.zig @@ -310,7 +310,7 @@ fn processMarkdown( defer prog.end(); var prog_step = prog.start("Compile", 1); - var build = std.process.Child.init(&.{ compiler_path, "/dev/stdin" }, gpa); + var build = std.process.Child.init(&.{ compiler_path, "build", "/dev/stdin" }, gpa); build.stdin_behavior = .Pipe; build.stdout_behavior = .Pipe; build.stderr_behavior = .Pipe; diff --git a/test/behavior/arithmetic b/test/behavior/arithmetic index 564cb1b0fb9e2763f4af27fe52764062f9e995a8..8ca10d6edb7f5cefaa1d73e525e18fdc4c7a5f6a 100644 --- a/test/behavior/arithmetic +++ b/test/behavior/arithmetic @@ -16,7 +16,7 @@ fn main { assertEqual(subInt(528, -43), i(571)); } -fn f(a: float) -> float { a } +fn f(a: float) { a } fn addFloat(a: float, b: float) -> float { a + b @@ -42,7 +42,7 @@ fn divFloat(a: float, b: float) -> float { // a ** b // } -fn i(a: int) -> int { a } +fn i(a: int) { a } fn addInt(a: int, b: int) -> int { a + b diff --git a/test/behavior/array b/test/behavior/array index 16ca89f2285eaafd9dfae16261f738eb5236c166..0058e44528c91ac04b09e0750f94674f4dc73441 100644 --- a/test/behavior/array +++ b/test/behavior/array @@ -11,4 +11,4 @@ fn main { assertEqual(array[i(1) + 2] + array[i(2) + 2], i(2)); } -fn i(a: int) -> int { a } +fn i(a: int) { a } diff --git a/test/behavior/inferred_return_type b/test/behavior/inferred_return_type new file mode 100644 index 0000000000000000000000000000000000000000..67c5cbc51abe8a608ea4e2a0fc56cbff6e2aac57 --- /dev/null +++ b/test/behavior/inferred_return_type @@ -0,0 +1,10 @@ +let assertEqual = @extern("assertEqual"); + +fn getString() { + "hello" +} + +fn main { + let str = getString(); + assertEqual(str, "hello"); +} diff --git a/test/behavior/tuple b/test/behavior/tuple index 088915e7301707580c30f035b94ae81f2a5dd980..1884912b9b8b076ac3db1d0fdb7f81380f3b88dc 100644 --- a/test/behavior/tuple +++ b/test/behavior/tuple @@ -1,8 +1,7 @@ let assertEqual = @extern("assertEqual"); fn hello() -> tuple (int, float) { - let x: tuple (int, float) = (5, 2.5); - x + (5, 2.5) } fn main { -- 2.54.0