commit f2bbd8a548c9a707aa121c58fe8c8a97666b84f2 (tree)
parent dac1cd77505ef9fa493e069549c139d74e31081f
Author: Andrew Kelley <andrew@ziglang.org>
Date: Thu, 3 Sep 2020 17:22:57 -0400
Merge pull request #6242 from Vexu/stage2
Stage2: slicing and split container scope from file scope
Diffstat:
10 files changed, 394 insertions(+), 111 deletions(-)
diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig
@@ -1175,6 +1175,7 @@ pub const Tokenizer = struct {
},
.num_dot_dec => switch (c) {
'.' => {
+ result.id = .IntegerLiteral;
self.index -= 1;
state = .start;
break;
@@ -1183,7 +1184,6 @@ pub const Tokenizer = struct {
state = .float_exponent_unsigned;
},
'0'...'9' => {
- result.id = .FloatLiteral;
state = .float_fraction_dec;
},
else => {
@@ -1769,6 +1769,7 @@ test "tokenizer - number literals decimal" {
testTokenize("7", &[_]Token.Id{.IntegerLiteral});
testTokenize("8", &[_]Token.Id{.IntegerLiteral});
testTokenize("9", &[_]Token.Id{.IntegerLiteral});
+ testTokenize("1..", &[_]Token.Id{ .IntegerLiteral, .Ellipsis2 });
testTokenize("0a", &[_]Token.Id{ .Invalid, .Identifier });
testTokenize("9b", &[_]Token.Id{ .Invalid, .Identifier });
testTokenize("1z", &[_]Token.Id{ .Invalid, .Identifier });
diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig
@@ -125,7 +125,7 @@ pub const Decl = struct {
/// mapping them to an address in the output file.
/// Memory owned by this decl, using Module's allocator.
name: [*:0]const u8,
- /// The direct parent container of the Decl. This is either a `Scope.File` or `Scope.ZIRModule`.
+ /// The direct parent container of the Decl. This is either a `Scope.Container` or `Scope.ZIRModule`.
/// Reference to externally owned memory.
scope: *Scope,
/// The AST Node decl index or ZIR Inst index that contains this declaration.
@@ -217,9 +217,10 @@ pub const Decl = struct {
pub fn src(self: Decl) usize {
switch (self.scope.tag) {
- .file => {
- const file = @fieldParentPtr(Scope.File, "base", self.scope);
- const tree = file.contents.tree;
+ .container => {
+ const container = @fieldParentPtr(Scope.Container, "base", self.scope);
+ const tree = container.file_scope.contents.tree;
+ // TODO Container should have it's own decls()
const decl_node = tree.root_node.decls()[self.src_index];
return tree.token_locs[decl_node.firstToken()].start;
},
@@ -229,6 +230,7 @@ pub const Decl = struct {
const src_decl = module.decls[self.src_index];
return src_decl.inst.src;
},
+ .file,
.block => unreachable,
.gen_zir => unreachable,
.local_val => unreachable,
@@ -359,6 +361,7 @@ pub const Scope = struct {
.local_ptr => return self.cast(LocalPtr).?.gen_zir.arena,
.zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator,
.file => unreachable,
+ .container => unreachable,
}
}
@@ -368,15 +371,16 @@ pub const Scope = struct {
return switch (self.tag) {
.block => self.cast(Block).?.decl,
.gen_zir => self.cast(GenZIR).?.decl,
- .local_val => return self.cast(LocalVal).?.gen_zir.decl,
- .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl,
+ .local_val => self.cast(LocalVal).?.gen_zir.decl,
+ .local_ptr => self.cast(LocalPtr).?.gen_zir.decl,
.decl => self.cast(DeclAnalysis).?.decl,
.zir_module => null,
.file => null,
+ .container => null,
};
}
- /// Asserts the scope has a parent which is a ZIRModule or File and
+ /// Asserts the scope has a parent which is a ZIRModule or Container and
/// returns it.
pub fn namespace(self: *Scope) *Scope {
switch (self.tag) {
@@ -385,7 +389,8 @@ pub const Scope = struct {
.local_val => return self.cast(LocalVal).?.gen_zir.decl.scope,
.local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope,
.decl => return self.cast(DeclAnalysis).?.decl.scope,
- .zir_module, .file => return self,
+ .file => return &self.cast(File).?.root_container.base,
+ .zir_module, .container => return self,
}
}
@@ -399,8 +404,9 @@ pub const Scope = struct {
.local_val => unreachable,
.local_ptr => unreachable,
.decl => unreachable,
+ .file => unreachable,
.zir_module => return self.cast(ZIRModule).?.fullyQualifiedNameHash(name),
- .file => return self.cast(File).?.fullyQualifiedNameHash(name),
+ .container => return self.cast(Container).?.fullyQualifiedNameHash(name),
}
}
@@ -409,11 +415,12 @@ pub const Scope = struct {
switch (self.tag) {
.file => return self.cast(File).?.contents.tree,
.zir_module => unreachable,
- .decl => return self.cast(DeclAnalysis).?.decl.scope.cast(File).?.contents.tree,
- .block => return self.cast(Block).?.decl.scope.cast(File).?.contents.tree,
- .gen_zir => return self.cast(GenZIR).?.decl.scope.cast(File).?.contents.tree,
- .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope.cast(File).?.contents.tree,
- .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope.cast(File).?.contents.tree,
+ .decl => return self.cast(DeclAnalysis).?.decl.scope.cast(Container).?.file_scope.contents.tree,
+ .block => return self.cast(Block).?.decl.scope.cast(Container).?.file_scope.contents.tree,
+ .gen_zir => return self.cast(GenZIR).?.decl.scope.cast(Container).?.file_scope.contents.tree,
+ .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope.cast(Container).?.file_scope.contents.tree,
+ .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope.cast(Container).?.file_scope.contents.tree,
+ .container => return self.cast(Container).?.file_scope.contents.tree,
}
}
@@ -427,13 +434,15 @@ pub const Scope = struct {
.decl => unreachable,
.zir_module => unreachable,
.file => unreachable,
+ .container => unreachable,
};
}
- /// Asserts the scope has a parent which is a ZIRModule or File and
+ /// Asserts the scope has a parent which is a ZIRModule, Contaienr or File and
/// returns the sub_file_path field.
pub fn subFilePath(base: *Scope) []const u8 {
switch (base.tag) {
+ .container => return @fieldParentPtr(Container, "base", base).file_scope.sub_file_path,
.file => return @fieldParentPtr(File, "base", base).sub_file_path,
.zir_module => return @fieldParentPtr(ZIRModule, "base", base).sub_file_path,
.block => unreachable,
@@ -453,11 +462,13 @@ pub const Scope = struct {
.local_val => unreachable,
.local_ptr => unreachable,
.decl => unreachable,
+ .container => unreachable,
}
}
pub fn getSource(base: *Scope, module: *Module) ![:0]const u8 {
switch (base.tag) {
+ .container => return @fieldParentPtr(Container, "base", base).file_scope.getSource(module),
.file => return @fieldParentPtr(File, "base", base).getSource(module),
.zir_module => return @fieldParentPtr(ZIRModule, "base", base).getSource(module),
.gen_zir => unreachable,
@@ -471,8 +482,9 @@ pub const Scope = struct {
/// Asserts the scope is a namespace Scope and removes the Decl from the namespace.
pub fn removeDecl(base: *Scope, child: *Decl) void {
switch (base.tag) {
- .file => return @fieldParentPtr(File, "base", base).removeDecl(child),
+ .container => return @fieldParentPtr(Container, "base", base).removeDecl(child),
.zir_module => return @fieldParentPtr(ZIRModule, "base", base).removeDecl(child),
+ .file => unreachable,
.block => unreachable,
.gen_zir => unreachable,
.local_val => unreachable,
@@ -499,6 +511,7 @@ pub const Scope = struct {
.local_val => unreachable,
.local_ptr => unreachable,
.decl => unreachable,
+ .container => unreachable,
}
}
@@ -515,6 +528,8 @@ pub const Scope = struct {
zir_module,
/// .zig source code.
file,
+ /// struct, enum or union, every .file contains one of these.
+ container,
block,
decl,
gen_zir,
@@ -522,6 +537,38 @@ pub const Scope = struct {
local_ptr,
};
+ pub const Container = struct {
+ pub const base_tag: Tag = .container;
+ base: Scope = Scope{ .tag = base_tag },
+
+ file_scope: *Scope.File,
+
+ /// Direct children of the file.
+ decls: ArrayListUnmanaged(*Decl),
+
+ // TODO implement container types and put this in a status union
+ // ty: Type
+
+ pub fn deinit(self: *Container, gpa: *Allocator) void {
+ self.decls.deinit(gpa);
+ self.* = undefined;
+ }
+
+ pub fn removeDecl(self: *Container, child: *Decl) void {
+ for (self.decls.items) |item, i| {
+ if (item == child) {
+ _ = self.decls.swapRemove(i);
+ return;
+ }
+ }
+ }
+
+ pub fn fullyQualifiedNameHash(self: *Container, name: []const u8) NameHash {
+ // TODO container scope qualified names.
+ return std.zig.hashSrc(name);
+ }
+ };
+
pub const File = struct {
pub const base_tag: Tag = .file;
base: Scope = Scope{ .tag = base_tag },
@@ -544,8 +591,7 @@ pub const Scope = struct {
loaded_success,
},
- /// Direct children of the file.
- decls: ArrayListUnmanaged(*Decl),
+ root_container: Container,
pub fn unload(self: *File, gpa: *Allocator) void {
switch (self.status) {
@@ -569,20 +615,11 @@ pub const Scope = struct {
}
pub fn deinit(self: *File, gpa: *Allocator) void {
- self.decls.deinit(gpa);
+ self.root_container.deinit(gpa);
self.unload(gpa);
self.* = undefined;
}
- pub fn removeDecl(self: *File, child: *Decl) void {
- for (self.decls.items) |item, i| {
- if (item == child) {
- _ = self.decls.swapRemove(i);
- return;
- }
- }
- }
-
pub fn dumpSrc(self: *File, src: usize) void {
const loc = std.zig.findLineColumn(self.source.bytes, src);
std.debug.print("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 });
@@ -604,11 +641,6 @@ pub const Scope = struct {
.bytes => |bytes| return bytes,
}
}
-
- pub fn fullyQualifiedNameHash(self: *File, name: []const u8) NameHash {
- // We don't have struct scopes yet so this is currently just a simple name hash.
- return std.zig.hashSrc(name);
- }
};
pub const ZIRModule = struct {
@@ -861,7 +893,10 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {
.source = .{ .unloaded = {} },
.contents = .{ .not_available = {} },
.status = .never_loaded,
- .decls = .{},
+ .root_container = .{
+ .file_scope = root_scope,
+ .decls = .{},
+ },
};
break :blk &root_scope.base;
} else if (mem.endsWith(u8, options.root_pkg.root_src_path, ".zir")) {
@@ -969,7 +1004,7 @@ pub fn update(self: *Module) !void {
// to force a refresh we unload now.
if (self.root_scope.cast(Scope.File)) |zig_file| {
zig_file.unload(self.gpa);
- self.analyzeRootSrcFile(zig_file) catch |err| switch (err) {
+ self.analyzeContainer(&zig_file.root_container) catch |err| switch (err) {
error.AnalysisFail => {
assert(self.totalErrorCount() != 0);
},
@@ -1237,8 +1272,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
const tracy = trace(@src());
defer tracy.end();
- const file_scope = decl.scope.cast(Scope.File).?;
- const tree = try self.getAstTree(file_scope);
+ const container_scope = decl.scope.cast(Scope.Container).?;
+ const tree = try self.getAstTree(container_scope);
const ast_node = tree.root_node.decls()[decl.src_index];
switch (ast_node.tag) {
.FnProto => {
@@ -1698,10 +1733,12 @@ fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module {
}
}
-fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree {
+fn getAstTree(self: *Module, container_scope: *Scope.Container) !*ast.Tree {
const tracy = trace(@src());
defer tracy.end();
+ const root_scope = container_scope.file_scope;
+
switch (root_scope.status) {
.never_loaded, .unloaded_success => {
try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1);
@@ -1743,24 +1780,24 @@ fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree {
}
}
-fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
+fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void {
const tracy = trace(@src());
defer tracy.end();
// We may be analyzing it for the first time, or this may be
// an incremental update. This code handles both cases.
- const tree = try self.getAstTree(root_scope);
+ const tree = try self.getAstTree(container_scope);
const decls = tree.root_node.decls();
try self.work_queue.ensureUnusedCapacity(decls.len);
- try root_scope.decls.ensureCapacity(self.gpa, decls.len);
+ try container_scope.decls.ensureCapacity(self.gpa, decls.len);
// Keep track of the decls that we expect to see in this file so that
// we know which ones have been deleted.
var deleted_decls = std.AutoArrayHashMap(*Decl, void).init(self.gpa);
defer deleted_decls.deinit();
- try deleted_decls.ensureCapacity(root_scope.decls.items.len);
- for (root_scope.decls.items) |file_decl| {
+ try deleted_decls.ensureCapacity(container_scope.decls.items.len);
+ for (container_scope.decls.items) |file_decl| {
deleted_decls.putAssumeCapacityNoClobber(file_decl, {});
}
@@ -1773,7 +1810,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
const name_loc = tree.token_locs[name_tok];
const name = tree.tokenSliceLoc(name_loc);
- const name_hash = root_scope.fullyQualifiedNameHash(name);
+ const name_hash = container_scope.fullyQualifiedNameHash(name);
const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl));
if (self.decl_table.get(name_hash)) |decl| {
// Update the AST Node index of the decl, even if its contents are unchanged, it may
@@ -1801,8 +1838,8 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
}
}
} else {
- const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash);
- root_scope.decls.appendAssumeCapacity(new_decl);
+ const new_decl = try self.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash);
+ container_scope.decls.appendAssumeCapacity(new_decl);
if (fn_proto.getExternExportInlineToken()) |maybe_export_token| {
if (tree.token_ids[maybe_export_token] == .Keyword_export) {
self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
@@ -1812,7 +1849,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
} else if (src_decl.castTag(.VarDecl)) |var_decl| {
const name_loc = tree.token_locs[var_decl.name_token];
const name = tree.tokenSliceLoc(name_loc);
- const name_hash = root_scope.fullyQualifiedNameHash(name);
+ const name_hash = container_scope.fullyQualifiedNameHash(name);
const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl));
if (self.decl_table.get(name_hash)) |decl| {
// Update the AST Node index of the decl, even if its contents are unchanged, it may
@@ -1828,8 +1865,8 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
decl.contents_hash = contents_hash;
}
} else {
- const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash);
- root_scope.decls.appendAssumeCapacity(new_decl);
+ const new_decl = try self.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash);
+ container_scope.decls.appendAssumeCapacity(new_decl);
if (var_decl.getExternExportToken()) |maybe_export_token| {
if (tree.token_ids[maybe_export_token] == .Keyword_export) {
self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
@@ -1841,11 +1878,11 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
const name = try std.fmt.allocPrint(self.gpa, "__comptime_{}", .{name_index});
defer self.gpa.free(name);
- const name_hash = root_scope.fullyQualifiedNameHash(name);
+ const name_hash = container_scope.fullyQualifiedNameHash(name);
const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl));
- const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash);
- root_scope.decls.appendAssumeCapacity(new_decl);
+ const new_decl = try self.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash);
+ container_scope.decls.appendAssumeCapacity(new_decl);
self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
} else if (src_decl.castTag(.ContainerField)) |container_field| {
log.err("TODO: analyze container field", .{});
@@ -2591,6 +2628,72 @@ pub fn analyzeIsErr(self: *Module, scope: *Scope, src: usize, operand: *Inst) In
return self.fail(scope, src, "TODO implement analysis of iserr", .{});
}
+pub fn analyzeSlice(self: *Module, scope: *Scope, src: usize, array_ptr: *Inst, start: *Inst, end_opt: ?*Inst, sentinel_opt: ?*Inst) InnerError!*Inst {
+ const ptr_child = switch (array_ptr.ty.zigTypeTag()) {
+ .Pointer => array_ptr.ty.elemType(),
+ else => return self.fail(scope, src, "expected pointer, found '{}'", .{array_ptr.ty}),
+ };
+
+ var array_type = ptr_child;
+ const elem_type = switch (ptr_child.zigTypeTag()) {
+ .Array => ptr_child.elemType(),
+ .Pointer => blk: {
+ if (ptr_child.isSinglePointer()) {
+ if (ptr_child.elemType().zigTypeTag() == .Array) {
+ array_type = ptr_child.elemType();
+ break :blk ptr_child.elemType().elemType();
+ }
+
+ return self.fail(scope, src, "slice of single-item pointer", .{});
+ }
+ break :blk ptr_child.elemType();
+ },
+ else => return self.fail(scope, src, "slice of non-array type '{}'", .{ptr_child}),
+ };
+
+ const slice_sentinel = if (sentinel_opt) |sentinel| blk: {
+ const casted = try self.coerce(scope, elem_type, sentinel);
+ break :blk try self.resolveConstValue(scope, casted);
+ } else null;
+
+ var return_ptr_size: std.builtin.TypeInfo.Pointer.Size = .Slice;
+ var return_elem_type = elem_type;
+ if (end_opt) |end| {
+ if (end.value()) |end_val| {
+ if (start.value()) |start_val| {
+ const start_u64 = start_val.toUnsignedInt();
+ const end_u64 = end_val.toUnsignedInt();
+ if (start_u64 > end_u64) {
+ return self.fail(scope, src, "out of bounds slice", .{});
+ }
+
+ const len = end_u64 - start_u64;
+ const array_sentinel = if (array_type.zigTypeTag() == .Array and end_u64 == array_type.arrayLen())
+ array_type.sentinel()
+ else
+ slice_sentinel;
+ return_elem_type = try self.arrayType(scope, len, array_sentinel, elem_type);
+ return_ptr_size = .One;
+ }
+ }
+ }
+ const return_type = try self.ptrType(
+ scope,
+ src,
+ return_elem_type,
+ if (end_opt == null) slice_sentinel else null,
+ 0, // TODO alignment
+ 0,
+ 0,
+ !ptr_child.isConstPtr(),
+ ptr_child.isAllowzeroPtr(),
+ ptr_child.isVolatilePtr(),
+ return_ptr_size,
+ );
+
+ return self.fail(scope, src, "TODO implement analysis of slice", .{});
+}
+
/// Asserts that lhs and rhs types are both numeric.
pub fn cmpNumeric(
self: *Module,
@@ -2801,6 +2904,12 @@ pub fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Ty
prev_inst = next_inst;
continue;
}
+ if (next_inst.ty.zigTypeTag() == .Undefined)
+ continue;
+ if (prev_inst.ty.zigTypeTag() == .Undefined) {
+ prev_inst = next_inst;
+ continue;
+ }
if (prev_inst.ty.isInt() and
next_inst.ty.isInt() and
prev_inst.ty.isSignedInt() == next_inst.ty.isSignedInt())
@@ -3052,6 +3161,7 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Err
self.failed_files.putAssumeCapacityNoClobber(scope, err_msg);
},
.file => unreachable,
+ .container => unreachable,
}
return error.AnalysisFail;
}
diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig
@@ -275,16 +275,16 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
.ErrorType => return rlWrap(mod, scope, rl, try errorType(mod, scope, node.castTag(.ErrorType).?)),
.For => return forExpr(mod, scope, rl, node.castTag(.For).?),
.ArrayAccess => return arrayAccess(mod, scope, rl, node.castTag(.ArrayAccess).?),
+ .Slice => return rlWrap(mod, scope, rl, try sliceExpr(mod, scope, node.castTag(.Slice).?)),
.Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?),
.Comptime => return comptimeKeyword(mod, scope, rl, node.castTag(.Comptime).?),
+ .OrElse => return orelseExpr(mod, scope, rl, node.castTag(.OrElse).?),
.Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}),
.Range => return mod.failNode(scope, node, "TODO implement astgen.expr for .Range", .{}),
- .OrElse => return mod.failNode(scope, node, "TODO implement astgen.expr for .OrElse", .{}),
.Await => return mod.failNode(scope, node, "TODO implement astgen.expr for .Await", .{}),
.Resume => return mod.failNode(scope, node, "TODO implement astgen.expr for .Resume", .{}),
.Try => return mod.failNode(scope, node, "TODO implement astgen.expr for .Try", .{}),
- .Slice => return mod.failNode(scope, node, "TODO implement astgen.expr for .Slice", .{}),
.ArrayInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializer", .{}),
.ArrayInitializerDot => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializerDot", .{}),
.StructInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .StructInitializer", .{}),
@@ -790,13 +790,31 @@ fn errorType(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!*
}
fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch) InnerError!*zir.Inst {
+ return orelseCatchExpr(mod, scope, rl, node.lhs, node.op_token, .iserr, .unwrap_err_unsafe, node.rhs, node.payload);
+}
+
+fn orelseExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst {
+ return orelseCatchExpr(mod, scope, rl, node.lhs, node.op_token, .isnull, .unwrap_optional_unsafe, node.rhs, null);
+}
+
+fn orelseCatchExpr(
+ mod: *Module,
+ scope: *Scope,
+ rl: ResultLoc,
+ lhs: *ast.Node,
+ op_token: ast.TokenIndex,
+ cond_op: zir.Inst.Tag,
+ unwrap_op: zir.Inst.Tag,
+ rhs: *ast.Node,
+ payload_node: ?*ast.Node,
+) InnerError!*zir.Inst {
const tree = scope.tree();
- const src = tree.token_locs[node.op_token].start;
+ const src = tree.token_locs[op_token].start;
- const err_union_ptr = try expr(mod, scope, .ref, node.lhs);
- // TODO we could avoid an unnecessary copy if .iserr took a pointer
- const err_union = try addZIRUnOp(mod, scope, src, .deref, err_union_ptr);
- const cond = try addZIRUnOp(mod, scope, src, .iserr, err_union);
+ const operand_ptr = try expr(mod, scope, .ref, lhs);
+ // TODO we could avoid an unnecessary copy if .iserr, .isnull took a pointer
+ const err_union = try addZIRUnOp(mod, scope, src, .deref, operand_ptr);
+ const cond = try addZIRUnOp(mod, scope, src, cond_op, err_union);
var block_scope: Scope.GenZIR = .{
.parent = scope,
@@ -825,55 +843,55 @@ fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch)
.inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block },
};
- var err_scope: Scope.GenZIR = .{
+ var then_scope: Scope.GenZIR = .{
.parent = scope,
.decl = block_scope.decl,
.arena = block_scope.arena,
.instructions = .{},
};
- defer err_scope.instructions.deinit(mod.gpa);
+ defer then_scope.instructions.deinit(mod.gpa);
var err_val_scope: Scope.LocalVal = undefined;
- const err_sub_scope = blk: {
- const payload = node.payload orelse
- break :blk &err_scope.base;
+ const then_sub_scope = blk: {
+ const payload = payload_node orelse
+ break :blk &then_scope.base;
const err_name = tree.tokenSlice(payload.castTag(.Payload).?.error_symbol.firstToken());
if (mem.eql(u8, err_name, "_"))
- break :blk &err_scope.base;
+ break :blk &then_scope.base;
- const unwrapped_err_ptr = try addZIRUnOp(mod, &err_scope.base, src, .unwrap_err_code, err_union_ptr);
+ const unwrapped_err_ptr = try addZIRUnOp(mod, &then_scope.base, src, .unwrap_err_code, operand_ptr);
err_val_scope = .{
- .parent = &err_scope.base,
- .gen_zir = &err_scope,
+ .parent = &then_scope.base,
+ .gen_zir = &then_scope,
.name = err_name,
- .inst = try addZIRUnOp(mod, &err_scope.base, src, .deref, unwrapped_err_ptr),
+ .inst = try addZIRUnOp(mod, &then_scope.base, src, .deref, unwrapped_err_ptr),
};
break :blk &err_val_scope.base;
};
- _ = try addZIRInst(mod, &err_scope.base, src, zir.Inst.Break, .{
+ _ = try addZIRInst(mod, &then_scope.base, src, zir.Inst.Break, .{
.block = block,
- .operand = try expr(mod, err_sub_scope, branch_rl, node.rhs),
+ .operand = try expr(mod, then_sub_scope, branch_rl, rhs),
}, .{});
- var not_err_scope: Scope.GenZIR = .{
+ var else_scope: Scope.GenZIR = .{
.parent = scope,
.decl = block_scope.decl,
.arena = block_scope.arena,
.instructions = .{},
};
- defer not_err_scope.instructions.deinit(mod.gpa);
+ defer else_scope.instructions.deinit(mod.gpa);
- const unwrapped_payload = try addZIRUnOp(mod, ¬_err_scope.base, src, .unwrap_err_unsafe, err_union_ptr);
- _ = try addZIRInst(mod, ¬_err_scope.base, src, zir.Inst.Break, .{
+ const unwrapped_payload = try addZIRUnOp(mod, &else_scope.base, src, unwrap_op, operand_ptr);
+ _ = try addZIRInst(mod, &else_scope.base, src, zir.Inst.Break, .{
.block = block,
.operand = unwrapped_payload,
}, .{});
- condbr.positionals.then_body = .{ .instructions = try err_scope.arena.dupe(*zir.Inst, err_scope.instructions.items) };
- condbr.positionals.else_body = .{ .instructions = try not_err_scope.arena.dupe(*zir.Inst, not_err_scope.instructions.items) };
- return rlWrap(mod, scope, rl, &block.base);
+ condbr.positionals.then_body = .{ .instructions = try then_scope.arena.dupe(*zir.Inst, then_scope.instructions.items) };
+ condbr.positionals.else_body = .{ .instructions = try else_scope.arena.dupe(*zir.Inst, else_scope.instructions.items) };
+ return rlWrapPtr(mod, scope, rl, &block.base);
}
/// Return whether the identifier names of two tokens are equal. Resolves @"" tokens without allocating.
@@ -933,6 +951,36 @@ fn arrayAccess(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Array
return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.ElemPtr, .{ .array_ptr = array_ptr, .index = index }, .{}));
}
+fn sliceExpr(mod: *Module, scope: *Scope, node: *ast.Node.Slice) InnerError!*zir.Inst {
+ const tree = scope.tree();
+ const src = tree.token_locs[node.rtoken].start;
+
+ const usize_type = try addZIRInstConst(mod, scope, src, .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.usize_type),
+ });
+
+ const array_ptr = try expr(mod, scope, .ref, node.lhs);
+ const start = try expr(mod, scope, .{ .ty = usize_type }, node.start);
+
+ if (node.end == null and node.sentinel == null) {
+ return try addZIRBinOp(mod, scope, src, .slice_start, array_ptr, start);
+ }
+
+ const end = if (node.end) |end| try expr(mod, scope, .{ .ty = usize_type }, end) else null;
+ // we could get the child type here, but it is easier to just do it in semantic analysis.
+ const sentinel = if (node.sentinel) |sentinel| try expr(mod, scope, .none, sentinel) else null;
+
+ return try addZIRInst(
+ mod,
+ scope,
+ src,
+ zir.Inst.Slice,
+ .{ .array_ptr = array_ptr, .start = start },
+ .{ .end = end, .sentinel = sentinel },
+ );
+}
+
fn deref(mod: *Module, scope: *Scope, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst {
const tree = scope.tree();
const src = tree.token_locs[node.rtoken].start;
diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig
@@ -132,7 +132,7 @@ pub fn generateSymbol(
.Array => {
// TODO populate .debug_info for the array
if (typed_value.val.cast(Value.Payload.Bytes)) |payload| {
- if (typed_value.ty.arraySentinel()) |sentinel| {
+ if (typed_value.ty.sentinel()) |sentinel| {
try code.ensureCapacity(code.items.len + payload.data.len + 1);
code.appendSliceAssumeCapacity(payload.data);
const prev_len = code.items.len;
@@ -436,8 +436,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
try branch_stack.append(.{});
const src_data: struct {lbrace_src: usize, rbrace_src: usize, source: []const u8} = blk: {
- if (module_fn.owner_decl.scope.cast(Module.Scope.File)) |scope_file| {
- const tree = scope_file.contents.tree;
+ if (module_fn.owner_decl.scope.cast(Module.Scope.Container)) |container_scope| {
+ const tree = container_scope.file_scope.contents.tree;
const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?;
const block = fn_proto.getBodyNode().?.castTag(.Block).?;
const lbrace_src = tree.token_locs[block.lbrace].start;
diff --git a/src-self-hosted/codegen/c.zig b/src-self-hosted/codegen/c.zig
@@ -85,7 +85,7 @@ fn genArray(file: *C, decl: *Decl) !void {
const name = try map(file.base.allocator, mem.span(decl.name));
defer file.base.allocator.free(name);
if (tv.val.cast(Value.Payload.Bytes)) |payload|
- if (tv.ty.arraySentinel()) |sentinel|
+ if (tv.ty.sentinel()) |sentinel|
if (sentinel.toUnsignedInt() == 0)
try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data })
else
diff --git a/src-self-hosted/link/Elf.zig b/src-self-hosted/link/Elf.zig
@@ -1656,8 +1656,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
try dbg_line_buffer.ensureCapacity(26);
const line_off: u28 = blk: {
- if (decl.scope.cast(Module.Scope.File)) |scope_file| {
- const tree = scope_file.contents.tree;
+ if (decl.scope.cast(Module.Scope.Container)) |container_scope| {
+ const tree = container_scope.file_scope.contents.tree;
const file_ast_decls = tree.root_node.decls();
// TODO Look into improving the performance here by adding a token-index-to-line
// lookup table. Currently this involves scanning over the source code for newlines.
@@ -2157,8 +2157,8 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec
const tracy = trace(@src());
defer tracy.end();
- const scope_file = decl.scope.cast(Module.Scope.File).?;
- const tree = scope_file.contents.tree;
+ const container_scope = decl.scope.cast(Module.Scope.Container).?;
+ const tree = container_scope.file_scope.contents.tree;
const file_ast_decls = tree.root_node.decls();
// TODO Look into improving the performance here by adding a token-index-to-line
// lookup table. Currently this involves scanning over the source code for newlines.
diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig
@@ -163,7 +163,7 @@ pub const Type = extern union {
// Hot path for common case:
if (a.castPointer()) |a_payload| {
if (b.castPointer()) |b_payload| {
- return eql(a_payload.pointee_type, b_payload.pointee_type);
+ return a.tag() == b.tag() and eql(a_payload.pointee_type, b_payload.pointee_type);
}
}
const is_slice_a = isSlice(a);
@@ -189,10 +189,10 @@ pub const Type = extern union {
.Array => {
if (a.arrayLen() != b.arrayLen())
return false;
- if (a.elemType().eql(b.elemType()))
+ if (!a.elemType().eql(b.elemType()))
return false;
- const sentinel_a = a.arraySentinel();
- const sentinel_b = b.arraySentinel();
+ const sentinel_a = a.sentinel();
+ const sentinel_b = b.sentinel();
if (sentinel_a) |sa| {
if (sentinel_b) |sb| {
return sa.eql(sb);
@@ -501,9 +501,9 @@ pub const Type = extern union {
.noreturn,
=> return out_stream.writeAll(@tagName(t)),
- .enum_literal => return out_stream.writeAll("@TypeOf(.EnumLiteral)"),
- .@"null" => return out_stream.writeAll("@TypeOf(null)"),
- .@"undefined" => return out_stream.writeAll("@TypeOf(undefined)"),
+ .enum_literal => return out_stream.writeAll("@Type(.EnumLiteral)"),
+ .@"null" => return out_stream.writeAll("@Type(.Null)"),
+ .@"undefined" => return out_stream.writeAll("@Type(.Undefined)"),
.@"anyframe" => return out_stream.writeAll("anyframe"),
.anyerror_void_error_union => return out_stream.writeAll("anyerror!void"),
@@ -630,8 +630,8 @@ pub const Type = extern union {
const payload = @fieldParentPtr(Payload.Pointer, "base", ty.ptr_otherwise);
if (payload.sentinel) |some| switch (payload.size) {
.One, .C => unreachable,
- .Many => try out_stream.writeAll("[*:{}]"),
- .Slice => try out_stream.writeAll("[:{}]"),
+ .Many => try out_stream.print("[*:{}]", .{some}),
+ .Slice => try out_stream.print("[:{}]", .{some}),
} else switch (payload.size) {
.One => try out_stream.writeAll("*"),
.Many => try out_stream.writeAll("[*]"),
@@ -1341,6 +1341,81 @@ pub const Type = extern union {
};
}
+ pub fn isAllowzeroPtr(self: Type) bool {
+ return switch (self.tag()) {
+ .u8,
+ .i8,
+ .u16,
+ .i16,
+ .u32,
+ .i32,
+ .u64,
+ .i64,
+ .usize,
+ .isize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ .c_longdouble,
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_void,
+ .bool,
+ .void,
+ .type,
+ .anyerror,
+ .comptime_int,
+ .comptime_float,
+ .noreturn,
+ .@"null",
+ .@"undefined",
+ .array,
+ .array_sentinel,
+ .array_u8,
+ .array_u8_sentinel_0,
+ .fn_noreturn_no_args,
+ .fn_void_no_args,
+ .fn_naked_noreturn_no_args,
+ .fn_ccc_void_no_args,
+ .function,
+ .int_unsigned,
+ .int_signed,
+ .single_mut_pointer,
+ .single_const_pointer,
+ .many_const_pointer,
+ .many_mut_pointer,
+ .c_const_pointer,
+ .c_mut_pointer,
+ .const_slice,
+ .mut_slice,
+ .single_const_pointer_to_comptime_int,
+ .const_slice_u8,
+ .optional,
+ .optional_single_mut_pointer,
+ .optional_single_const_pointer,
+ .enum_literal,
+ .error_union,
+ .@"anyframe",
+ .anyframe_T,
+ .anyerror_void_error_union,
+ .error_set,
+ .error_set_single,
+ => false,
+
+ .pointer => {
+ const payload = @fieldParentPtr(Payload.Pointer, "base", self.ptr_otherwise);
+ return payload.@"allowzero";
+ },
+ };
+ }
+
/// Asserts that the type is an optional
pub fn isPtrLikeOptional(self: Type) bool {
switch (self.tag()) {
@@ -1585,8 +1660,8 @@ pub const Type = extern union {
};
}
- /// Asserts the type is an array or vector.
- pub fn arraySentinel(self: Type) ?Value {
+ /// Asserts the type is an array, pointer or vector.
+ pub fn sentinel(self: Type) ?Value {
return switch (self.tag()) {
.u8,
.i8,
@@ -1626,16 +1701,8 @@ pub const Type = extern union {
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.function,
- .pointer,
- .single_const_pointer,
- .single_mut_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
.const_slice,
.mut_slice,
- .single_const_pointer_to_comptime_int,
.const_slice_u8,
.int_unsigned,
.int_signed,
@@ -1651,7 +1718,18 @@ pub const Type = extern union {
.error_set_single,
=> unreachable,
- .array, .array_u8 => return null,
+ .single_const_pointer,
+ .single_mut_pointer,
+ .many_const_pointer,
+ .many_mut_pointer,
+ .c_const_pointer,
+ .c_mut_pointer,
+ .single_const_pointer_to_comptime_int,
+ .array,
+ .array_u8,
+ => return null,
+
+ .pointer => return self.cast(Payload.Pointer).?.sentinel,
.array_sentinel => return self.cast(Payload.ArraySentinel).?.sentinel,
.array_u8_sentinel_0 => return Value.initTag(.zero),
};
diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig
@@ -301,15 +301,15 @@ pub const Value = extern union {
.comptime_int_type => return out_stream.writeAll("comptime_int"),
.comptime_float_type => return out_stream.writeAll("comptime_float"),
.noreturn_type => return out_stream.writeAll("noreturn"),
- .null_type => return out_stream.writeAll("@TypeOf(null)"),
- .undefined_type => return out_stream.writeAll("@TypeOf(undefined)"),
+ .null_type => return out_stream.writeAll("@Type(.Null)"),
+ .undefined_type => return out_stream.writeAll("@Type(.Undefined)"),
.fn_noreturn_no_args_type => return out_stream.writeAll("fn() noreturn"),
.fn_void_no_args_type => return out_stream.writeAll("fn() void"),
.fn_naked_noreturn_no_args_type => return out_stream.writeAll("fn() callconv(.Naked) noreturn"),
.fn_ccc_void_no_args_type => return out_stream.writeAll("fn() callconv(.C) void"),
.single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"),
.const_slice_u8_type => return out_stream.writeAll("[]const u8"),
- .enum_literal_type => return out_stream.writeAll("@TypeOf(.EnumLiteral)"),
+ .enum_literal_type => return out_stream.writeAll("@Type(.EnumLiteral)"),
.anyframe_type => return out_stream.writeAll("anyframe"),
.null_value => return out_stream.writeAll("null"),
diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig
@@ -231,6 +231,10 @@ pub const Inst = struct {
const_slice_type,
/// Create a pointer type with attributes
ptr_type,
+ /// Slice operation `array_ptr[start..end:sentinel]`
+ slice,
+ /// Slice operation with just start `lhs[rhs..]`
+ slice_start,
/// Write a value to a pointer. For loading, see `deref`.
store,
/// String Literal. Makes an anonymous Decl and then takes a pointer to it.
@@ -343,6 +347,7 @@ pub const Inst = struct {
.xor,
.error_union_type,
.merge_error_sets,
+ .slice_start,
=> BinOp,
.block,
@@ -380,6 +385,7 @@ pub const Inst = struct {
.ptr_type => PtrType,
.enum_literal => EnumLiteral,
.error_set => ErrorSet,
+ .slice => Slice,
};
}
@@ -481,6 +487,8 @@ pub const Inst = struct {
.error_union_type,
.bitnot,
.error_set,
+ .slice,
+ .slice_start,
=> false,
.@"break",
@@ -961,6 +969,20 @@ pub const Inst = struct {
},
kw_args: struct {},
};
+
+ pub const Slice = struct {
+ pub const base_tag = Tag.slice;
+ base: Inst,
+
+ positionals: struct {
+ array_ptr: *Inst,
+ start: *Inst,
+ },
+ kw_args: struct {
+ end: ?*Inst = null,
+ sentinel: ?*Inst = null,
+ },
+ };
};
pub const ErrorMsg = struct {
@@ -2574,7 +2596,7 @@ const EmitZIR = struct {
var len_pl = Value.Payload.Int_u64{ .int = ty.arrayLen() };
const len = Value.initPayload(&len_pl.base);
- const inst = if (ty.arraySentinel()) |sentinel| blk: {
+ const inst = if (ty.sentinel()) |sentinel| blk: {
const inst = try self.arena.allocator.create(Inst.ArrayTypeSentinel);
inst.* = .{
.base = .{
diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig
@@ -132,6 +132,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
.error_union_type => return analyzeInstErrorUnionType(mod, scope, old_inst.castTag(.error_union_type).?),
.anyframe_type => return analyzeInstAnyframeType(mod, scope, old_inst.castTag(.anyframe_type).?),
.error_set => return analyzeInstErrorSet(mod, scope, old_inst.castTag(.error_set).?),
+ .slice => return analyzeInstSlice(mod, scope, old_inst.castTag(.slice).?),
+ .slice_start => return analyzeInstSliceStart(mod, scope, old_inst.castTag(.slice_start).?),
}
}
@@ -1172,6 +1174,22 @@ fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inne
return mod.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{});
}
+fn analyzeInstSlice(mod: *Module, scope: *Scope, inst: *zir.Inst.Slice) InnerError!*Inst {
+ const array_ptr = try resolveInst(mod, scope, inst.positionals.array_ptr);
+ const start = try resolveInst(mod, scope, inst.positionals.start);
+ const end = if (inst.kw_args.end) |end| try resolveInst(mod, scope, end) else null;
+ const sentinel = if (inst.kw_args.sentinel) |sentinel| try resolveInst(mod, scope, sentinel) else null;
+
+ return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, end, sentinel);
+}
+
+fn analyzeInstSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
+ const array_ptr = try resolveInst(mod, scope, inst.positionals.lhs);
+ const start = try resolveInst(mod, scope, inst.positionals.rhs);
+
+ return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null);
+}
+
fn analyzeInstShl(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
return mod.fail(scope, inst.base.src, "TODO implement analyzeInstShl", .{});
}
@@ -1239,6 +1257,12 @@ fn analyzeInstArithmetic(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn
if (casted_lhs.value()) |lhs_val| {
if (casted_rhs.value()) |rhs_val| {
+ if (lhs_val.isUndef() or rhs_val.isUndef()) {
+ return mod.constInst(scope, inst.base.src, .{
+ .ty = resolved_type,
+ .val = Value.initTag(.undef),
+ });
+ }
return analyzeInstComptimeOp(mod, scope, scalar_type, inst, lhs_val, rhs_val);
}
}