From a78c131c45a7f6e79080e8a7965e19f35f656fd8 Mon Sep 17 00:00:00 2001 From: chloe caruso Date: Mon, 27 May 2024 17:19:49 -0700 Subject: [PATCH] introduce assignment targets and validate them --- CHANGELOG.md | 7 + src/compiler/Air.zig | 2 +- src/compiler/Ast.zig | 14 +- src/compiler/InternPool.zig | 3 +- src/compiler/JsNodeGen.zig | 2 +- src/compiler/JsTree.zig | 2 +- src/compiler/Report.zig | 256 ++++++++++++++++++---------- src/compiler/Sema.zig | 25 +-- src/compiler/Teg.zig | 3 +- src/compiler/Tokenizer.zig | 20 +-- src/compiler/Transform.zig | 177 ++++++++++++++----- src/compiler/{dod.zig => array.zig} | 0 src/compiler/util.zig | 15 ++ 13 files changed, 368 insertions(+), 158 deletions(-) rename src/compiler/{dod.zig => array.zig} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 152375646f0bf0faf1ca457a13467471a7f8bd49..d22d8243f42922400d7aea953d7e99af67086007 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.1.3 + +- feat(sema): coersion: numeric_literal -> float +- feat(transform): lhs of assignments now validated +- feat(transform): mark useless discard +- fix(sema): properly coerce assignments + ## 0.1.2 - feat: semantic analysis can return formatted errors to the user diff --git a/src/compiler/Air.zig b/src/compiler/Air.zig index 0811efcf84df67ea59921320092586c9ca6eafcb..4252ffa290e9295ce7318681d943d70aad6298ed 100644 --- a/src/compiler/Air.zig +++ b/src/compiler/Air.zig @@ -315,6 +315,6 @@ const Value = InternPool.Value; const Type = InternPool.Type; const Teg = @import("Teg.zig"); -const dod = @import("dod.zig"); +const dod = @import("array.zig"); const Unused = enum(u32) { zero = 0 }; diff --git a/src/compiler/Ast.zig b/src/compiler/Ast.zig index f1eb29fcf62758e5e113d3a4e1573cf1b7d18c57..e11c6d8fa3ed6be8c5d0d1be924fcfe2707e062f 100644 --- a/src/compiler/Ast.zig +++ b/src/compiler/Ast.zig @@ -381,11 +381,12 @@ pub const Node = struct { pub const Error = error{ OutOfMemory, ParseFail }; pub const Location = struct { - // TODO: add an ordinal number type, replace with Ordinal(usize) - line: usize, - column: usize, - line_start: usize, - line_end: usize, + line: Ordinal(u32), + column: Ordinal(u32), + // Byte offset of the start of the line + line_start: ByteOffset, + // Byte offset of the end of the line + line_end: ByteOffset, }; pub const Span = struct { @@ -672,3 +673,6 @@ const TokenList = Tokenizer.Token.List; const ByteOffset = Tokenizer.ByteOffset; const Report = @import("Report.zig"); + +const util = @import("util.zig"); +const Ordinal = util.Ordinal; diff --git a/src/compiler/InternPool.zig b/src/compiler/InternPool.zig index cac26a5554ec06c64b09c90e9d1c1c4e0c55c321..7863bf6debc6688cc2765fa2bd99bfef73f3294d 100644 --- a/src/compiler/InternPool.zig +++ b/src/compiler/InternPool.zig @@ -171,6 +171,7 @@ pub const Value = enum(u32) { float_zero, nan, empty_string, + standard_library, none = std.math.maxInt(u32), _, @@ -1170,7 +1171,7 @@ const Cache = @import("Cache.zig"); const Teg = @import("Teg.zig"); const Air = @import("Air.zig"); -const dod = @import("dod.zig"); +const dod = @import("array.zig"); const Float = dod.Float; const tracer = @import("tracer"); diff --git a/src/compiler/JsNodeGen.zig b/src/compiler/JsNodeGen.zig index 8b61b9ea17ba66eea2e1b1c74b9dadcf564adc2d..f3b801e05e8e7b37b38e3903f81bcc39a5e1381e 100644 --- a/src/compiler/JsNodeGen.zig +++ b/src/compiler/JsNodeGen.zig @@ -696,5 +696,5 @@ const LocalIndex = JsTree.LocalIndex; const Air = @import("Air.zig"); -const dod = @import("dod.zig"); +const dod = @import("array.zig"); const tracer = @import("tracer"); diff --git a/src/compiler/JsTree.zig b/src/compiler/JsTree.zig index 81186431c72fbcea50d645d3526f830e2586d07c..291b621802fe1957910128273fd77b223064ee14 100644 --- a/src/compiler/JsTree.zig +++ b/src/compiler/JsTree.zig @@ -252,6 +252,6 @@ const String = InternPool.String; const Comp = @import("Comp.zig"); const Decl = Comp.Decl; -const dod = @import("dod.zig"); +const dod = @import("array.zig"); const Unused = @import("Ast.zig").Unused; diff --git a/src/compiler/Report.zig b/src/compiler/Report.zig index f0191aa5086807fae8458779e512965d82acfb1a..814a0f3510f5d342912441a6b39b4a66df3cd735 100644 --- a/src/compiler/Report.zig +++ b/src/compiler/Report.zig @@ -169,6 +169,48 @@ pub const Err = union(enum) { }); } }, + assign_builtin: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgNode( + data.node, + "cannot re-assign built-in \"{s}\"", + .{ctx.ast.basicNodeSlice(data.node)}, + ); + } + }, + invalid_assignment_symbol: struct { + node: NodeIndex, + decl: NodeIndex, + kind: UnusedKind, + + fn build(data: @This(), ctx: *MsgContext) !void { + const id = switch (ctx.ast.nodeTag(data.decl)) { + .param_decl => ctx.ast.fullParamDecl(data.decl).id, + .fn_decl => ctx.ast.fullFnDecl(data.decl).id, + .var_decl => ctx.ast.fullVarDecl(data.decl).id, + else => unreachable, // not a valid symbol node + }; + + try ctx.msgToken(ctx.ast.idToken(data.node), "cannot assign to {s} \"{s}\"", .{ + @tagName(data.kind), + id, + }); + try ctx.msgToken(ctx.ast.idToken(data.decl), "declared here", .{}); + } + }, + invalid_assignment_target: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgNode( + data.node, + "invalid assignment target", + .{}, + ); + } + }, }; pub const Warn = union(enum) { @@ -177,7 +219,6 @@ pub const Warn = union(enum) { unused: struct { node: NodeIndex, kind: UnusedKind, - // shadowed: ?NodeIndex = null, fn build(data: @This(), ctx: *MsgContext) !void { const id = switch (ctx.ast.nodeTag(data.node)) { @@ -192,15 +233,23 @@ pub const Warn = union(enum) { id, }); - // if (data.shadowed) |node| { - // try ctx.msgToken(ctx.ast.idToken(node), "{s} was re-declared here", .{ - // @tagName(data.kind), - // }); - // } - try ctx.msgAlone("if this was intentional, discard it with \"_ = {s};\"", .{id}); } }, + not_mutated_var: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + const id = switch (ctx.ast.nodeTag(data.node)) { + .var_decl => ctx.ast.fullVarDecl(data.node).id, + else => unreachable, // not a valid variable symbol node + }; + + try ctx.msgToken(ctx.ast.idToken(data.node), "variable \"{s}\" not mutated", .{ + id, + }); + } + }, invalid_discard: struct { node: NodeIndex, @@ -208,6 +257,20 @@ pub const Warn = union(enum) { try ctx.msgToken(ctx.ast.nodeToken(data.node), "\"let\" is not required for a discard", .{}); } }, + discard_builtin: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgToken(ctx.ast.nodeToken(data.node), "discarding a built-in symbol is meaningless", .{}); + } + }, + unnecessary_discard: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgToken(ctx.ast.nodeToken(data.node), "useless discard", .{}); + } + }, dangling_identifier: struct { node: NodeIndex, @@ -219,9 +282,8 @@ pub const Warn = union(enum) { node: LazyNode, fn build(data: @This(), ctx: *MsgContext) !void { - const var_decl_node = try data.node.get(ctx); - const init_node = ctx.ast.nodeData(var_decl_node).var_decl.init; - try ctx.msgNode(init_node, "constant integer in a \"var\" has an unknown type, assuming \"float\"", .{}); + const node = try data.node.get(ctx); + try ctx.msgNode(node, "constant integer in a \"var\" has an unknown type, assuming \"float\"", .{}); try ctx.msgAlone("add \": float\" or \": int\" after the variable name to set the type", .{}); } }, @@ -346,7 +408,6 @@ pub fn formatItem( const need_load_ast = !(item_type == .@"error" and item.data.@"error" == .ast); const ast_data = if (need_load_ast) try file.getAst(comp) else undefined; - const ast = if (need_load_ast) &ast_data else null; var ctx: MsgContext = .{ .arena = alloc, @@ -392,92 +453,32 @@ pub fn formatItem( try writer.writeAll(component.message_utf8); try writer.writeAll("\n"); - const SliceLoc = struct { - loc: Location, - byte: ByteOffset, - slice: []const u8, - }; - const utf8_source = ctx.utf8_source; - const at: SliceLoc = switch (component.highlight) { - .alone => { - if (is_last) { - try tty.setColor(writer, .dim); - try writer.print(" {s}", .{"┴"}); - try writer.writeAll("\n"); - try tty.setColor(writer, .reset); - } - continue; - }, - inline .node, - .token, - .two_tokens, - => |payload, tag| loc: { - const token_index: TokenIndex = switch (tag) { - .node => ast.?.nodeToken(payload), - .token, .two_tokens => payload, - else => comptime unreachable, - }; - - const location = ast.?.tokenLocation(0, token_index); - const token = ast.?.tokens.get(token_index); - - break :loc .{ - .loc = location, - .slice = token.slice(ast.?.utf8_source), - .byte = token.offset, - }; - }, - - .token_stored => |token| loc: { - const location = Tokenizer.tokenLocation(ctx.utf8_source, 0, token.offset); - - break :loc .{ - .loc = location, - .slice = token.slice(utf8_source), - .byte = token.offset, - }; - }, - - inline .codepoint, - .codepoint_eol, - => |b, tag| loc: { - const len = switch (tag) { - .codepoint => std.unicode.utf8ByteSequenceLength(utf8_source[b]) catch 1, - .codepoint_eol => if (std.mem.indexOfScalar(u8, utf8_source[b..], '\n')) |n| - n - else - (utf8_source.len - b), - else => comptime unreachable, - }; - break :loc .{ - .byte = b, - .slice = utf8_source[b..][0..len], - .loc = .{ - .column = 0, - .line = 0, - .line_start = 0, - .line_end = utf8_source.len, - }, - }; - }, + const at = component.location orelse { + if (is_last) { + try tty.setColor(writer, .dim); + try writer.print(" {s}", .{"┴"}); + try writer.writeAll("\n"); + try tty.setColor(writer, .reset); + } + continue; }; const line_before = utf8_source[at.loc.line_start..at.byte]; const trimmed_before = std.mem.trimLeft(u8, line_before, " \t\r"); const line_after = utf8_source[at.byte + at.slice.len .. at.loc.line_end + 1]; - const width = std.fmt.count("{d}", .{at.loc.line}); + const width = std.fmt.count("{d}", .{at.loc.line.oneBased()}); try tty.setColor(writer, .dim); try writer.writeByteNTimes(' ', width); try writer.print("{s}{s}:{d}:{d}\n", .{ if (i == 1) "┬ " else "├ ", file.file_path, - at.loc.line, - at.loc.column, + at.loc.line.oneBased(), + at.loc.column.oneBased(), }); - try writer.print("{d}", .{at.loc.line}); + try writer.print("{d}", .{at.loc.line.oneBased()}); try writer.print("{s} ", .{"│"}); try tty.setColor(writer, .reset); try writer.writeAll(trimmed_before); @@ -593,6 +594,17 @@ pub const Builder = struct { } }; +pub const SliceLoc = struct { + loc: Location, + byte: ByteOffset, + slice: []const u8, +}; + +const Component = struct { + message_utf8: []const u8, + location: ?SliceLoc, +}; + pub const Highlight = union(enum) { // No source view alone, @@ -608,11 +620,64 @@ pub const Highlight = union(enum) { codepoint: ByteOffset, // Highlight from the start of a codepoint to '\n' codepoint_eol: ByteOffset, -}; -const Component = struct { - message_utf8: []const u8, - highlight: Highlight, + pub fn loc(highlight: Highlight, ast: *const Ast) ?SliceLoc { + return switch (highlight) { + .alone => null, + inline .node, + .token, + .two_tokens, + => |payload, tag| loc: { + const token_index: TokenIndex = switch (tag) { + .node => ast.nodeToken(payload), + .token, .two_tokens => payload, + else => comptime unreachable, + }; + + const location = ast.tokenLocation(0, token_index); + const token = ast.tokens.get(token_index); + + break :loc .{ + .loc = location, + .slice = token.slice(ast.utf8_source), + .byte = token.offset, + }; + }, + + .token_stored => |token| loc: { + const location = Tokenizer.tokenLocation(ast.utf8_source, 0, token.offset); + + break :loc .{ + .loc = location, + .slice = token.slice(ast.utf8_source), + .byte = token.offset, + }; + }, + inline .codepoint, + .codepoint_eol, + => |b, tag| loc: { + const len = switch (tag) { + .codepoint => std.unicode.utf8ByteSequenceLength(ast.utf8_source[b]) catch 1, + .codepoint_eol => if (std.mem.indexOfScalar(u8, ast.utf8_source[b..], '\n')) |n| + n + else + (ast.utf8_source.len - b), + else => comptime unreachable, + }; + break :loc .{ + .byte = b, + .slice = ast.utf8_source[b..][0..len], + .loc = .{ + // TODO: + .column = .start, + .line = .start, + .line_start = 0, + .line_end = @intCast(ast.utf8_source.len), + }, + }; + }, + }; + } }; /// This serves as a small API to make creating messages easier. @@ -636,7 +701,7 @@ pub const MsgContext = struct { const text = try std.fmt.allocPrint(m.arena, fmt, args); try m.components.append(m.arena, .{ .message_utf8 = text, - .highlight = highlight, + .location = highlight.loc(m.ast), }); } @@ -719,6 +784,21 @@ pub const LazyNode = struct { }, else => return base_node, }, + .memory => switch (node_tag) { + .var_decl => switch (lazy.operand) { + .operand => return data.var_decl.init, + else => {}, + }, + else => {}, + }, + .store => { + assert(node_tag.isAssignBin()); + switch (lazy.operand) { + .lhs => return data.bin.lhs, + .rhs => return data.bin.rhs, + else => unreachable, + } + }, else => {}, } diff --git a/src/compiler/Sema.zig b/src/compiler/Sema.zig index f23e3daf92670c25e204e3d58f630508d43ea234..cae7f9f6fd9a8663acf6d0f2c55f06954ec7e15f 100644 --- a/src/compiler/Sema.zig +++ b/src/compiler/Sema.zig @@ -500,16 +500,16 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { assert(s.code.instTag(data.memory) == .memory); // corrupt store instruction const location = s.mapTeg(data.memory.toRef()).decode().index; + s.markInst(location); const operand = try s.resolveRefRuntime(data.operand); - - // try s.validateType( - // s.typeOfRef(operand).toValue(), - // s.tempAir().instData(.memory, location).ty.ip_index, - // ); - + const operand_coerced = try s.coerceRef( + in.op(.rhs), + operand, + s.typeOfRef(location.toRef()), + ); return try s.addInstRef(.store, .{ .memory = location, - .operand = operand, + .operand = operand_coerced, }); }, .as => { @@ -690,8 +690,13 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) !Value { }, else => return s.coersionError(src, from, to), }, - .float => { - // + .float => switch (from) { + .float => unreachable, // same type handled + .numeric_literal => if (value.toFloat(s.ip)) |val| + return try s.makeValue(.{ + .float = val, + }), + else => {}, }, .string => { // @@ -985,7 +990,7 @@ const InstOp = Teg.InstOp; const Air = @import("Air.zig"); const Report = @import("Report.zig"); const Ast = @import("Ast.zig"); -const dod = @import("dod.zig"); +const dod = @import("array.zig"); const tracer = @import("tracer"); const InternPool = @import("InternPool.zig"); diff --git a/src/compiler/Teg.zig b/src/compiler/Teg.zig index 31f2b06ecf5a24eb902fb14b57a4c0024cb96a6e..5b76ced3658ab57f233eca18e6396439a6a21573 100644 --- a/src/compiler/Teg.zig +++ b/src/compiler/Teg.zig @@ -224,6 +224,7 @@ pub const Inst = struct { float_zero, nan, empty_string, + standard_library, /// This Ref does not correspond to any instruction or constant /// value and may instead be used as a sentinel to indicate null. @@ -472,7 +473,7 @@ const optionalTokenIndex = OptionalTokenIndex.init; const optionalNodeIndex = OptionalNodeIndex.init; const optionalExtraIndex = OptionalExtraIndex.init; -const dod = @import("dod.zig"); +const dod = @import("array.zig"); const Float = dod.Float; const InternPool = @import("InternPool.zig"); diff --git a/src/compiler/Tokenizer.zig b/src/compiler/Tokenizer.zig index 687307b791a79fcf5b1c4e89c692e174ec963bb2..9afadd8b6918d36cab142ccf359eb25584502c2a 100644 --- a/src/compiler/Tokenizer.zig +++ b/src/compiler/Tokenizer.zig @@ -949,10 +949,10 @@ pub fn tokenLocation( token_start: ByteOffset, ) Location { var loc: Location = .{ - .line = 1, - .column = 1, + .line = .start, + .column = .start, .line_start = start_offset, - .line_end = utf8_source.len, + .line_end = @intCast(utf8_source.len), }; // Scan to by line until we go past the token start @@ -960,25 +960,25 @@ pub fn tokenLocation( if (i >= token_start) { break; // Went past } - loc.line += 1; - loc.line_start = i + 1; + loc.line = loc.line.add(1); + loc.line_start = @intCast(i + 1); } const offset = loc.line_start; for (utf8_source[offset..], 0..) |c, i| { if (i + offset == token_start) { - loc.line_end = i + offset; + loc.line_end = @intCast(i + offset); while (loc.line_end < utf8_source.len and utf8_source[loc.line_end] != '\n') { loc.line_end += 1; } return loc; } if (c == '\n') { - loc.line += 1; - loc.column = 1; - loc.line_start = i + 1; + loc.line = loc.line.add(1); + loc.column = .start; + loc.line_start = @intCast(i + 1); } else { - loc.column += 1; + loc.column = loc.column.add(1); } } diff --git a/src/compiler/Transform.zig b/src/compiler/Transform.zig index 3e054977f01cd3ab95fb9e589fe0f61a579e1c8d..9652b3edc67059de5b4f81652cff4a753db1f0fb 100644 --- a/src/compiler/Transform.zig +++ b/src/compiler/Transform.zig @@ -79,34 +79,49 @@ const Scope = struct { const Symbol = struct { node: NodeIndex, data: Symbol.Data, + // TODO: optimize this structure used: bool = false, mutated: bool = false, + discarded: ?NodeIndex = null, const Data = union(enum) { declaration, param, - mutable: Inst.Ref, + mutable: Inst.Index, immutable: Inst.Ref, }; - }; - fn addUnusedWarnings(s: Scope, t: *Transform) !void { - for (s.symbols.values()) |value| { - if (!value.used) { + pub fn addWarnings(symbol: Symbol, t: *Transform) !void { + if (!symbol.mutated and symbol.data == .mutable) { + try t.report.warn(.not_mutated_var, .{ + .node = symbol.node, + }); + } + if (!symbol.used) { try t.report.warn(.unused, .{ - .node = value.node, - .kind = switch (value.data) { + .node = symbol.node, + .kind = switch (symbol.data) { .declaration => .function, .param => .param, .mutable => .variable, - .immutable => switch (t.ast.nodeTag(value.node)) { + .immutable => switch (t.ast.nodeTag(symbol.node)) { .param_decl => .param, else => .constant, }, }, }); + } else if (symbol.discarded) |discard| { + try t.report.warn(.unnecessary_discard, .{ + .node = discard, + }); } } + }; + + fn addWarnings(s: Scope, t: *Transform) !void { + for (s.symbols.values()) |value| { + try value.addWarnings(t); + } } fn deinit(s: *Scope, gpa: Allocator) void { @@ -212,7 +227,7 @@ const BuiltInIds = std.ComptimeStringMap(Inst.Ref, .{ .{ "bool", .bool_type }, .{ "noreturn", .noreturn_type }, .{ "type", .type_type }, - // todo: nan/infinity? + .{ "std", .standard_library }, }); fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.Index { @@ -303,7 +318,7 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I .extra_index = try t.extra.append(t.gpa, IndexList, declarations), }); - try t.scope().addUnusedWarnings(t); + try t.scope().addWarnings(t); return module; } @@ -400,7 +415,7 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A .extra_index = decl_extra, }); - try t.scope().addUnusedWarnings(t); + try t.scope().addWarnings(t); return func; } @@ -613,9 +628,8 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc .string = try t.addNullTerminatedString(id), }), }), - .mutable, - .immutable, - => |ref| ref, + .mutable => |idx| idx.toRef(), + .immutable => |ref| ref, .param => unreachable, //unresolved param }; }, @@ -650,14 +664,16 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc .assign => { const ast_bin = t.ast.nodeData(node).bin; - const lhs = try t.transformExpression(decl, ast_bin.lhs) orelse return null; - const rhs = try t.transformExpression(decl, ast_bin.rhs) orelse return null; + const lhs = try t.transformAssignmentTarget(decl, ast_bin.lhs) orelse + return .void; + const rhs = try t.transformExpression(decl, ast_bin.rhs) orelse + return null; const assign_inst = try t.addInstRef(.{ .tag = .store, .data = EncodedData.encode(.store, .{ .src_node = decl.nodeOffset(node), .extra_index = try t.extra.append(t.gpa, Inst.Store, .{ - .memory = lhs.toIndex() orelse @panic("TODO: convert this into Ref"), + .memory = lhs, .operand = rhs, }), }), @@ -709,7 +725,12 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc @tagName(tag), ); const ast_bin = t.ast.nodeData(node).bin; - const lhs = try t.transformExpression(decl, ast_bin.lhs) orelse return null; + const lhs = if (is_assign) + try t.transformAssignmentTarget(decl, ast_bin.lhs) orelse + return .void + else + try t.transformExpression(decl, ast_bin.lhs) orelse + return null; const rhs = try t.transformExpression(decl, ast_bin.rhs) orelse return null; const compute_inst = try t.addInstRef(.{ @@ -717,7 +738,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc .data = EncodedData.encode(teg_tag, .{ .src_node = decl.nodeOffset(node), .extra_index = try t.extra.append(t.gpa, Inst.Bin, .{ - .lhs = lhs, + .lhs = if (is_assign) lhs.toRef() else lhs, .rhs = rhs, }), }), @@ -729,7 +750,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc .data = EncodedData.encode(.store, .{ .src_node = decl.nodeOffset(node), .extra_index = try t.extra.append(t.gpa, Inst.Store, .{ - .memory = lhs.toIndex() orelse @panic("TODO: convert this into Ref"), + .memory = lhs, .operand = compute_inst, }), }), @@ -1042,6 +1063,75 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc } } +fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex) Allocator.Error!?Inst.Index { + const node_tag = t.ast.nodes.items(.tag)[node]; + + const trace = tracer.traceFmt(@src(), "{s}", .{@tagName(node_tag)}); + defer trace.end(); + + switch (node_tag) { + .root, + .param_decl, + .var_decl, + .fn_decl, + => unreachable, // not an expression + + .id => { + const id = t.ast.basicNodeSlice(node); + assert(!mem.eql(u8, id, "_")); // discards are not assignment targets + + if (BuiltInIds.has(id)) { + try t.report.err(.assign_builtin, .{ .node = node }); + return null; + } + + if (mem.startsWith(u8, id, "@")) { + @panic("TODO: string identifiers"); + } + + const lookup = t.lookupId(id) orelse { + try t.report.err(.unknown_identifier, .{ .node = node }); + return null; + }; + + return switch (lookup.data) { + .mutable => |inst| { + lookup.mutated = true; + return inst; + }, + else => { + lookup.used = true; + + try t.report.err(.invalid_assignment_symbol, .{ + .node = node, + .decl = lookup.node, + .kind = switch (lookup.data) { + .mutable => unreachable, + .declaration => .function, + .param => .param, + .immutable => switch (t.ast.nodeTag(lookup.node)) { + .param_decl => .param, + else => .constant, + }, + }, + }); + return null; + }, + }; + }, + + // TODO: allow field expressions, but these need to emit a different store node + + else => { + _ = try t.transformExpression(decl, node); + try t.report.err(.invalid_assignment_target, .{ + .node = node, + }); + return null; + }, + } +} + fn transformBlockStatement( t: *Transform, decl: BlockContext, @@ -1063,8 +1153,30 @@ fn transformBlockStatement( if (mem.eql(u8, var_decl.id, "_")) { try t.report.warn(.invalid_discard, .{ .node = node }); - const ref = try t.transformExpression(decl, var_decl.init) orelse - return null; + const ref = switch (t.ast.nodeTag(var_decl.init)) { + .id => { + const id = t.ast.basicNodeSlice(node); + if (BuiltInIds.has(id)) { + try t.report.warn(.discard_builtin, .{ .node = node }); + return .void; + } + + if (mem.startsWith(u8, id, "@")) { + @panic("TODO: string identifiers"); + } + + const lookup = t.lookupId(id) orelse { + try t.report.err(.unknown_identifier, .{ .node = node }); + return .void; + }; + + lookup.discarded = node; + + return .void; + }, + else => try t.transformExpression(decl, var_decl.init) orelse + return null, + }; return try t.addInstRef(.{ .tag = .discard, @@ -1115,29 +1227,14 @@ fn transformBlockStatement( // if re-declaring, apply unused warnings now if (gop.found_existing) { - const value = gop.value_ptr.*; - if (!value.used) { - try t.report.warn(.unused, .{ - .node = value.node, - .kind = switch (value.data) { - .declaration => .function, - .param => .param, - .mutable => .variable, - .immutable => switch (t.ast.nodeTag(value.node)) { - .param_decl => .param, - else => .constant, - }, - }, - // .shadowed = node, - }); - } + try gop.value_ptr.addWarnings(t); } gop.value_ptr.* = .{ .node = node, .data = switch (var_decl.is_const) { true => .{ .immutable = mem_ref }, - false => .{ .mutable = mem_ref }, + false => .{ .mutable = mem_ref.toIndex().? }, }, }; @@ -1352,7 +1449,7 @@ const IndexList = Teg.IndexList; const EncodedData = Teg.EncodedData; const ExtraIndex = Teg.ExtraIndex; -const dod = @import("dod.zig"); +const dod = @import("array.zig"); const ExtraStore = dod.ExtraStore(.teg, .@"var"); const Float = dod.Float; diff --git a/src/compiler/dod.zig b/src/compiler/array.zig similarity index 100% rename from src/compiler/dod.zig rename to src/compiler/array.zig diff --git a/src/compiler/util.zig b/src/compiler/util.zig index f4c51ab4cc6eb35fdcd80465008b9e12672344fb..6e244c52b8da94449206f1cf1ce0fcedd0fc369b 100644 --- a/src/compiler/util.zig +++ b/src/compiler/util.zig @@ -19,7 +19,9 @@ pub fn detectTTYFromWriter(writer: anytype) std.io.tty.Config { pub fn Ordinal(Int: type) type { comptime assert(@typeInfo(Int).Int.signedness == .unsigned); return enum(Int) { + start = 0, _, + pub fn fromZeroBased(int: Int) @This() { assert(int != std.math.maxInt(Int)); return @enumFromInt(int); @@ -34,6 +36,19 @@ pub fn Ordinal(Int: type) type { pub fn oneBased(ord: @This()) Int { return @intFromEnum(ord) + 1; } + + pub fn add(ord: @This(), inc: Int) @This() { + return fromZeroBased(ord.zeroBased() + inc); + } + + pub fn format( + _: @This(), + comptime _: []const u8, + _: std.fmt.FormatOptions, + _: anytype, + ) !void { + @compileError("Do not format `Ordinal` directly. Call oneBased or zeroBased to specify."); + } }; } -- 2.54.0