diff --git a/schema.json b/schema.json index 1ea33a8d0..5f8bf649c 100644 --- a/schema.json +++ b/schema.json @@ -84,11 +84,6 @@ "type": "boolean", "default": false }, - "dangerous_comptime_experiments_do_not_enable": { - "description": "Whether to use the comptime interpreter", - "type": "boolean", - "default": false - }, "skip_std_references": { "description": "When true, skips searching for references in std. Improves lookup speed for functions in user's code. Renaming and go-to-definition will continue to work as is", "type": "boolean", diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig deleted file mode 100644 index 8bafe272c..000000000 --- a/src/ComptimeInterpreter.zig +++ /dev/null @@ -1,1305 +0,0 @@ -//! Hacky comptime interpreter, courtesy of midnight code run fuelled by spite; -//! hope that one day this can use async... <33 -//! -//! Can be enabled by setting the `dangerous_comptime_experiments_do_not_enable` config option to `true`. - -const std = @import("std"); -const builtin = @import("builtin"); -const ast = @import("ast.zig"); -const zig = std.zig; -const Ast = zig.Ast; -const analysis = @import("analysis.zig"); -const offsets = @import("offsets.zig"); -const DocumentStore = @import("DocumentStore.zig"); - -pub const InternPool = @import("analyser/InternPool.zig"); -pub const ErrorMsg = @import("analyser/error_msg.zig").ErrorMsg; -pub const Index = InternPool.Index; -pub const Key = InternPool.Key; -pub const ComptimeInterpreter = @This(); - -const log = std.log.scoped(.zls_comptime_interpreter); - -allocator: std.mem.Allocator, -ip: *InternPool, -document_store: *DocumentStore, -uri: DocumentStore.Uri, -namespaces: std.MultiArrayList(Namespace) = .{}, -has_analyzed_root: bool = false, -mutex: std.Thread.Mutex = .{}, - -pub fn getHandle(interpreter: *ComptimeInterpreter) *DocumentStore.Handle { - // This interpreter is loaded from a known-valid handle so a valid handle must exist - return interpreter.document_store.getHandle(interpreter.uri).?; -} - -pub fn recordError( - interpreter: *ComptimeInterpreter, - node_idx: Ast.Node.Index, - code: []const u8, - comptime fmt: []const u8, - args: anytype, -) error{OutOfMemory}!void { - const message = try std.fmt.allocPrint(interpreter.allocator, fmt, args); - errdefer interpreter.allocator.free(message); - const handle = interpreter.getHandle(); - try handle.analysis_errors.append(interpreter.document_store.allocator, .{ - .loc = offsets.nodeToLoc(handle.tree, node_idx), - .code = code, - .message = message, - }); -} - -pub fn deinit(interpreter: *ComptimeInterpreter) void { - for ( - interpreter.namespaces.items(.decls), - interpreter.namespaces.items(.usingnamespaces), - ) |*decls, *usingnamespaces| { - decls.deinit(interpreter.allocator); - usingnamespaces.deinit(interpreter.allocator); - } - interpreter.namespaces.deinit(interpreter.allocator); -} - -pub const Value = struct { - interpreter: *ComptimeInterpreter, - - node_idx: Ast.Node.Index, - /// this stores both the type and the value - index: Index, -}; -// pub const Comptimeness = enum { @"comptime", runtime }; - -pub const Namespace = struct { - /// always points to Namespace or Index.none - parent: Namespace.Index, - node_idx: Ast.Node.Index, - /// Will be a struct, enum, union, opaque or .none - ty: InternPool.Index, - decls: std.StringArrayHashMapUnmanaged(InternPool.Decl.Index) = .{}, - usingnamespaces: std.ArrayListUnmanaged(InternPool.Decl.Index) = .{}, - - pub const Index = InternPool.NamespaceIndex; - - // TODO: Actually use this value - // comptimeness: Comptimeness, - - pub fn getLabel(self: Namespace, tree: Ast) ?Ast.TokenIndex { - const token_tags = tree.tokens.items(.tag); - - switch (tree.nodes.items(.tag)[self.node_idx]) { - .block_two, - .block_two_semicolon, - .block, - .block_semicolon, - => { - const lbrace = tree.nodes.items(.main_token)[self.node_idx]; - if (token_tags[lbrace - 1] == .colon and token_tags[lbrace - 2] == .identifier) { - return lbrace - 2; - } - - return null; - }, - else => return null, - } - } -}; - -pub const InterpretResult = union(enum) { - @"break": ?[]const u8, - break_with_value: struct { - label: ?[]const u8, - value: Value, - }, - value: Value, - @"return", - return_with_value: Value, - nothing, - - pub fn maybeGetValue(result: InterpretResult) ?Value { - return switch (result) { - .break_with_value => |v| v.value, - .value => |v| v, - .return_with_value => |v| v, - else => null, - }; - } - - pub fn getValue(result: InterpretResult) error{ExpectedValue}!Value { - return result.maybeGetValue() orelse error.ExpectedValue; - } -}; - -pub fn huntItDown( - interpreter: *ComptimeInterpreter, - namespace: Namespace.Index, - decl_name: []const u8, - options: InterpretOptions, -) InternPool.Decl.OptionalIndex { - _ = options; - - var current_namespace = namespace; - while (current_namespace != .none) { - const decls = interpreter.namespaces.items(.decls)[@intFromEnum(current_namespace)]; - defer current_namespace = interpreter.namespaces.items(.parent)[@intFromEnum(current_namespace)]; - - if (decls.get(decl_name)) |decl| { - return decl.toOptional(); - } - } - - return .none; -} - -// Might be useful in the future -pub const InterpretOptions = struct {}; - -pub const InterpretError = std.mem.Allocator.Error || std.fmt.ParseIntError || std.fmt.ParseFloatError || error{ - InvalidCharacter, - InvalidBase, - ExpectedValue, - InvalidOperation, - CriticalAstFailure, - InvalidBuiltin, - IdentifierNotFound, - MissingArguments, - ImportFailure, - InvalidCast, -}; - -pub fn interpret( - interpreter: *ComptimeInterpreter, - node_idx: Ast.Node.Index, - namespace: Namespace.Index, - options: InterpretOptions, -) InterpretError!InterpretResult { - const tree = interpreter.getHandle().tree; - const tags = tree.nodes.items(.tag); - const data = tree.nodes.items(.data); - const main_tokens = tree.nodes.items(.main_token); - - switch (tags[node_idx]) { - .container_decl, - .container_decl_trailing, - .container_decl_arg, - .container_decl_arg_trailing, - .container_decl_two, - .container_decl_two_trailing, - // .tagged_union, // TODO: Fix these - // .tagged_union_trailing, - // .tagged_union_two, - // .tagged_union_two_trailing, - // .tagged_union_enum_tag, - // .tagged_union_enum_tag_trailing, - .root, - => { - try interpreter.namespaces.append(interpreter.allocator, .{ - .parent = namespace, - .node_idx = node_idx, - .ty = .none, - }); - const container_namespace = @as(Namespace.Index, @enumFromInt(interpreter.namespaces.len - 1)); - - const struct_index = try interpreter.ip.createStruct(interpreter.allocator, .{ - .fields = .{}, - .owner_decl = .none, // TODO - .namespace = container_namespace, - .layout = .auto, // TODO - .backing_int_ty = .none, // TODO - .status = .none, // TODO - }); - var struct_info = interpreter.ip.getStructMut(struct_index); - - var buffer: [2]Ast.Node.Index = undefined; - - const container_decl = tree.fullContainerDecl(&buffer, node_idx).?; - for (container_decl.ast.members) |member| { - const container_field = tree.fullContainerField(member) orelse { - _ = try interpreter.interpret(member, container_namespace, options); - continue; - }; - - const init_value = try (try interpreter.interpret(container_field.ast.type_expr, container_namespace, .{})).getValue(); - - const default_value = if (container_field.ast.value_expr == 0) - Index.none - else - (try (try interpreter.interpret(container_field.ast.value_expr, container_namespace, .{})).getValue()).index; // TODO check ty - - const init_value_ty = interpreter.ip.typeOf(init_value.index); - if (init_value_ty != .type_type) { - try interpreter.recordError( - container_field.ast.type_expr, - "expected_type", - "expected type 'type', found '{}'", - .{init_value_ty.fmt(interpreter.ip)}, - ); - continue; - } - - const field_name = tree.tokenSlice(container_field.ast.main_token); - const field_name_index = try interpreter.ip.string_pool.getOrPutString(interpreter.allocator, field_name); - - try struct_info.fields.put(interpreter.allocator, field_name_index, .{ - .ty = init_value.index, - .default_value = default_value, - .alignment = 0, // TODO, - .is_comptime = false, // TODO - }); - } - - const struct_type = try interpreter.ip.get(interpreter.allocator, Key{ .struct_type = struct_index }); - interpreter.namespaces.items(.ty)[@intFromEnum(container_namespace)] = struct_type; - - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .index = struct_type, - } }; - }, - .error_set_decl => { - // TODO - return InterpretResult{ .nothing = {} }; - }, - .global_var_decl, - .local_var_decl, - .aligned_var_decl, - .simple_var_decl, - => { - var decls: *std.StringArrayHashMapUnmanaged(InternPool.Decl.Index) = &interpreter.namespaces.items(.decls)[@intFromEnum(namespace)]; - - const name = analysis.getDeclName(tree, node_idx).?; - const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{ - .name = try interpreter.ip.string_pool.getOrPutString(interpreter.allocator, name), - .node_idx = node_idx, - .index = .none, - .alignment = 0, // TODO - .address_space = .generic, // TODO - .src_namespace = namespace, - .is_pub = true, // TODO - .is_exported = false, // TODO - }); - - const gop = try decls.getOrPutValue(interpreter.allocator, name, decl_index); - if (gop.found_existing) { - return InterpretResult{ .nothing = {} }; - } - - const var_decl = tree.fullVarDecl(node_idx).?; - - const type_value = blk: { - if (var_decl.ast.type_node == 0) break :blk null; - const result = interpreter.interpret(var_decl.ast.type_node, namespace, .{}) catch break :blk null; - break :blk result.maybeGetValue(); - }; - const init_value = blk: { - if (var_decl.ast.init_node == 0) break :blk null; - const result = interpreter.interpret(var_decl.ast.init_node, namespace, .{}) catch break :blk null; - break :blk result.maybeGetValue(); - }; - - if (type_value == null and init_value == null) return InterpretResult{ .nothing = {} }; - - if (type_value) |v| { - if (interpreter.ip.typeOf(v.index) != .type_type) { - return InterpretResult{ .nothing = {} }; - } - } - // TODO coerce `init_value` into `type_value` - - const decl = interpreter.ip.getDeclMut(decl_index); - decl.index = if (type_value) |v| try interpreter.ip.getUnknown(interpreter.allocator, v.index) else init_value.?.index; - - // TODO: Am I a dumbo shrimp? (e.g. is this tree shaking correct? works on my machine so like...) - - // if (scope.?.scopeKind() != .container) { - // if (scope.?.node_idx != 0) - // _ = try decls.getPtr(name).?.getValue(); - - return InterpretResult{ .nothing = {} }; - }, - .block, - .block_semicolon, - .block_two, - .block_two_semicolon, - => { - try interpreter.namespaces.append(interpreter.allocator, .{ - .parent = namespace, - .node_idx = node_idx, - .ty = .none, - }); - const block_namespace = @as(Namespace.Index, @enumFromInt(interpreter.namespaces.len - 1)); - - var buffer: [2]Ast.Node.Index = undefined; - const statements = ast.blockStatements(tree, node_idx, &buffer).?; - - for (statements) |idx| { - const ret = try interpreter.interpret(idx, block_namespace, options); - switch (ret) { - .@"break" => |lllll| { - const maybe_block_label_string = if (interpreter.namespaces.get(@intFromEnum(namespace)).getLabel(tree)) |i| tree.tokenSlice(i) else null; - if (lllll) |l| { - if (maybe_block_label_string) |ls| { - if (std.mem.eql(u8, l, ls)) { - return InterpretResult{ .nothing = {} }; - } else return ret; - } else return ret; - } else { - return InterpretResult{ .nothing = {} }; - } - }, - .break_with_value => |bwv| { - const maybe_block_label_string = if (interpreter.namespaces.get(@intFromEnum(namespace)).getLabel(tree)) |i| tree.tokenSlice(i) else null; - - if (bwv.label) |l| { - if (maybe_block_label_string) |ls| { - if (std.mem.eql(u8, l, ls)) { - return InterpretResult{ .value = bwv.value }; - } else return ret; - } else return ret; - } else { - return InterpretResult{ .value = bwv.value }; - } - }, - .@"return", .return_with_value => return ret, - else => {}, - } - } - - return InterpretResult{ .nothing = {} }; - }, - .identifier => { - const identifier = offsets.nodeToSlice(tree, node_idx); - - const simples = std.StaticStringMap(Index).initComptime(.{ - .{ "anyerror", Index.anyerror_type }, - .{ "anyframe", Index.anyframe_type }, - .{ "anyopaque", Index.anyopaque_type }, - .{ "bool", Index.bool_type }, - .{ "c_int", Index.c_int_type }, - .{ "c_long", Index.c_long_type }, - .{ "c_longdouble", Index.c_longdouble_type }, - .{ "c_longlong", Index.c_longlong_type }, - .{ "c_short", Index.c_short_type }, - .{ "c_uint", Index.c_uint_type }, - .{ "c_ulong", Index.c_ulong_type }, - .{ "c_ulonglong", Index.c_ulonglong_type }, - .{ "c_ushort", Index.c_ushort_type }, - .{ "comptime_float", Index.comptime_float_type }, - .{ "comptime_int", Index.comptime_int_type }, - .{ "f128", Index.f128_type }, - .{ "f16", Index.f16_type }, - .{ "f32", Index.f32_type }, - .{ "f64", Index.f64_type }, - .{ "f80", Index.f80_type }, - .{ "false", Index.bool_false }, - .{ "isize", Index.isize_type }, - .{ "noreturn", Index.noreturn_type }, - .{ "null", Index.null_value }, - .{ "true", Index.bool_true }, - .{ "type", Index.type_type }, - .{ "undefined", Index.undefined_value }, - .{ "usize", Index.usize_type }, - .{ "void", Index.void_type }, - }); - - if (simples.get(identifier)) |index| { - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .index = index, - } }; - } - - if (identifier.len >= 2 and (identifier[0] == 'u' or identifier[0] == 'i')) blk: { - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .index = try interpreter.ip.get(interpreter.allocator, Key{ .int_type = .{ - .signedness = if (identifier[0] == 'u') .unsigned else .signed, - .bits = std.fmt.parseInt(u16, identifier[1..], 10) catch break :blk, - } }), - } }; - } - - // Logic to find identifiers in accessible scopes - if (interpreter.huntItDown(namespace, identifier, options).unwrap()) |decl_index| { - const decl = interpreter.ip.getDecl(decl_index); - if (decl.index == .none) return InterpretResult{ .nothing = {} }; - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = decl.node_idx, - .index = decl.index, - } }; - } - - try interpreter.recordError( - node_idx, - "undeclared_identifier", - "use of undeclared identifier '{s}'", - .{identifier}, - ); - // return error.IdentifierNotFound; - return InterpretResult{ .nothing = {} }; - }, - .field_access => { - if (data[node_idx].rhs == 0) return error.CriticalAstFailure; - const field_name = tree.tokenSlice(data[node_idx].rhs); - - var ir = try interpreter.interpret(data[node_idx].lhs, namespace, options); - const ir_value = try ir.getValue(); - - const val = ir_value.index; - const ty = interpreter.ip.typeOf(val); - - const inner_ty = switch (interpreter.ip.indexToKey(ty)) { - .pointer_type => |pointer_info| if (pointer_info.flags.size == .One) pointer_info.elem_type else ty, - else => ty, - }; - - switch (interpreter.ip.indexToKey(inner_ty)) { - .simple_type => |simple| switch (simple) { - .type => blk: { - if (val == .none) break :blk; - - const namespace_index = interpreter.ip.getNamespace(val); - if (namespace_index != .none) { - if (interpreter.huntItDown(namespace_index, field_name, options).unwrap()) |decl_index| { - const decl = interpreter.ip.getDecl(decl_index); - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .index = decl.index, - } }; - } - } - - switch (interpreter.ip.indexToKey(val)) { - .unknown_value => { - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = data[node_idx].rhs, - .index = .unknown_unknown, - } }; - }, - .error_set_type => |error_set_info| { // TODO - _ = error_set_info; - }, - .union_type => {}, // TODO - .enum_type => |enum_index| { // TODO - const enum_info = interpreter.ip.getEnum(enum_index); - const field_name_index = interpreter.ip.string_pool.getString(field_name) orelse break :blk; - const field = enum_info.fields.get(field_name_index) orelse break :blk; - _ = field; - return InterpretResult{ - .value = Value{ - .interpreter = interpreter, - .node_idx = data[node_idx].rhs, - .index = .unknown_unknown, // TODO - }, - }; - }, - else => {}, - } - }, - else => {}, - }, - .pointer_type => |pointer_info| { - switch (pointer_info.flags.size) { - .Many, .C => {}, - .One => { - switch (interpreter.ip.indexToKey(pointer_info.elem_type)) { - .array_type => |array_info| { - if (std.mem.eql(u8, field_name, "len")) { - return InterpretResult{ - .value = Value{ - .interpreter = interpreter, - .node_idx = data[node_idx].rhs, - .index = try interpreter.ip.get(interpreter.allocator, .{ .int_u64_value = .{ - .ty = .usize_type, - .int = array_info.len, - } }), - }, - }; - } - }, - else => {}, - } - }, - .Slice => { - if (std.mem.eql(u8, field_name, "ptr")) { - var many_ptr_info = InternPool.Key{ .pointer_type = pointer_info }; - many_ptr_info.pointer_type.flags.size = .Many; - return InterpretResult{ - .value = Value{ - .interpreter = interpreter, - .node_idx = data[node_idx].rhs, - // TODO resolve ptr of Slice - .index = try interpreter.ip.get(interpreter.allocator, .{ - .unknown_value = .{ .ty = try interpreter.ip.get(interpreter.allocator, many_ptr_info) }, - }), - }, - }; - } else if (std.mem.eql(u8, field_name, "len")) { - return InterpretResult{ - .value = Value{ - .interpreter = interpreter, - .node_idx = data[node_idx].rhs, - // TODO resolve length of Slice - .index = try interpreter.ip.getUnknown(interpreter.allocator, .usize_type), - }, - }; - } - }, - } - }, - .array_type => |array_info| { - if (std.mem.eql(u8, field_name, "len")) { - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = data[node_idx].rhs, - .index = try interpreter.ip.get(interpreter.allocator, .{ .int_u64_value = .{ - .ty = .usize_type, - .int = array_info.len, - } }), - } }; - } - }, - .optional_type => |optional_info| blk: { - if (!std.mem.eql(u8, field_name, "?")) break :blk; - - if (val == .type_type) { - try interpreter.recordError( - node_idx, - "null_unwrap", - "tried to unwrap optional of type `{}` which was null", - .{optional_info.payload_type.fmt(interpreter.ip)}, - ); - return error.InvalidOperation; - } - const result = switch (interpreter.ip.indexToKey(val)) { - .optional_value => |optional_val| optional_val.val, - .unknown_value => val, - else => return error.InvalidOperation, - }; - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = data[node_idx].rhs, - .index = result, - } }; - }, - .struct_type => |struct_index| blk: { - const struct_info = interpreter.ip.getStruct(struct_index); - const field_name_index = interpreter.ip.string_pool.getString(field_name) orelse break :blk; - const field_index = struct_info.fields.getIndex(field_name_index) orelse break :blk; - const field = struct_info.fields.values()[field_index]; - - const result = switch (interpreter.ip.indexToKey(val)) { - .aggregate => |aggregate| aggregate.values.at(@intCast(field_index), interpreter.ip), - .unknown_value => try interpreter.ip.get(interpreter.allocator, .{ - .unknown_value = .{ .ty = field.ty }, - }), - else => return error.InvalidOperation, - }; - - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = data[node_idx].rhs, - .index = result, - } }; - }, - .enum_type => |enum_info| { // TODO - _ = enum_info; - }, - .union_type => |union_info| { // TODO - _ = union_info; - }, - .int_type, - .error_union_type, - .error_set_type, - .function_type, - .tuple_type, - .vector_type, - .anyframe_type, - => {}, - - .simple_value, - .int_u64_value, - .int_i64_value, - .int_big_value, - .float_16_value, - .float_32_value, - .float_64_value, - .float_80_value, - .float_128_value, - .float_comptime_value, - => unreachable, - - .optional_value, - .slice, - .aggregate, - .union_value, - .null_value, - .error_value, - .undefined_value, - .unknown_value, - => unreachable, - } - - const accessed_ty = if (inner_ty == .type_type) val else inner_ty; - if (interpreter.ip.canHaveFields(accessed_ty)) { - try interpreter.recordError( - node_idx, - "undeclared_identifier", - "`{}` has no member '{s}'", - .{ accessed_ty.fmt(interpreter.ip), field_name }, - ); - } else { - try interpreter.recordError( - node_idx, - "invalid_field_access", - "`{}` does not support field access", - .{accessed_ty.fmt(interpreter.ip)}, - ); - } - return error.InvalidOperation; - }, - .grouped_expression => { - return try interpreter.interpret(data[node_idx].lhs, namespace, options); - }, - .@"break" => { - const label = if (data[node_idx].lhs == 0) null else tree.tokenSlice(data[node_idx].lhs); - return if (data[node_idx].rhs == 0) - InterpretResult{ .@"break" = label } - else - InterpretResult{ .break_with_value = .{ .label = label, .value = try (try interpreter.interpret(data[node_idx].rhs, namespace, options)).getValue() } }; - }, - .@"return" => { - return if (data[node_idx].lhs == 0) - InterpretResult{ .@"return" = {} } - else - InterpretResult{ .return_with_value = try (try interpreter.interpret(data[node_idx].lhs, namespace, options)).getValue() }; - }, - .@"if", - .if_simple, - => { - const if_info = ast.fullIf(tree, node_idx).?; - // if (options.observe_values) { - const ir = try interpreter.interpret(if_info.ast.cond_expr, namespace, options); - - const condition = (try ir.getValue()).index; - const condition_ty = interpreter.ip.typeOf(condition); - - switch (condition_ty) { - .bool_type => {}, - .unknown_type => return InterpretResult{ .nothing = {} }, - else => { - try interpreter.recordError( - if_info.ast.cond_expr, - "invalid_if_condition", - "expected `bool` but found `{}`", - .{condition_ty.fmt(interpreter.ip)}, - ); - return error.InvalidOperation; - }, - } - if (interpreter.ip.indexToKey(condition) == .unknown_value) { - return InterpretResult{ .nothing = {} }; - } - - std.debug.assert(condition == .bool_false or condition == .bool_true); - if (condition == .bool_true) { - return try interpreter.interpret(if_info.ast.then_expr, namespace, options); - } else { - if (if_info.ast.else_expr != 0) { - return try interpreter.interpret(if_info.ast.else_expr, namespace, options); - } - } - return InterpretResult{ .nothing = {} }; - }, - .equal_equal => { - var a = try interpreter.interpret(data[node_idx].lhs, namespace, options); - var b = try interpreter.interpret(data[node_idx].rhs, namespace, options); - const a_value = a.maybeGetValue() orelse return InterpretResult{ .nothing = {} }; - const b_value = b.maybeGetValue() orelse return InterpretResult{ .nothing = {} }; - return InterpretResult{ - .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .index = if (a_value.index == b_value.index) .bool_true else .bool_false, // TODO eql function required? - }, - }; - }, - .number_literal => { - const s = tree.getNodeSource(node_idx); - const nl = std.zig.parseNumberLiteral(s); - - if (nl == .failure) return error.CriticalAstFailure; - - const number_type = if (nl == .float) Index.comptime_float_type else Index.comptime_int_type; - - const value = switch (nl) { - .float => try interpreter.ip.get( - interpreter.allocator, - .{ .float_comptime_value = try std.fmt.parseFloat(f128, s) }, - ), - .int => if (s[0] == '-') - try interpreter.ip.get( - interpreter.allocator, - .{ .int_i64_value = .{ .ty = number_type, .int = try std.fmt.parseInt(i64, s, 0) } }, - ) - else - try interpreter.ip.get( - interpreter.allocator, - .{ .int_u64_value = .{ .ty = number_type, .int = try std.fmt.parseInt(u64, s, 0) } }, - ), - .big_int => |base| blk: { - var big_int = try std.math.big.int.Managed.init(interpreter.allocator); - defer big_int.deinit(); - const prefix_length: usize = if (base != .decimal) 2 else 0; - try big_int.setString(@intFromEnum(base), s[prefix_length..]); - std.debug.assert(number_type == .comptime_int_type); - break :blk try interpreter.ip.getBigInt(interpreter.allocator, number_type, big_int.toConst()); - }, - .failure => return error.CriticalAstFailure, - }; - - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .index = value, - } }; - }, - .assign, - .assign_bit_and, - .assign_bit_or, - .assign_shl, - .assign_shr, - .assign_bit_xor, - .assign_div, - .assign_sub, - .assign_sub_wrap, - .assign_mod, - .assign_add, - .assign_add_wrap, - .assign_mul, - .assign_mul_wrap, - => { - // TODO: Actually consider operators - - if (std.mem.eql(u8, tree.getNodeSource(data[node_idx].lhs), "_")) { - _ = try interpreter.interpret(data[node_idx].rhs, namespace, options); - return InterpretResult{ .nothing = {} }; - } - - const lhs = try interpreter.interpret(data[node_idx].lhs, namespace, options); - const rhs = try interpreter.interpret(data[node_idx].rhs, namespace, options); - - const to_val = try lhs.getValue(); - const from_val = try rhs.getValue(); - - const to_ty = interpreter.ip.typeOf(to_val.index); - const from_ty = interpreter.ip.typeOf(from_val.index); - _ = from_ty; - - var arena_allocator = std.heap.ArenaAllocator.init(interpreter.allocator); - defer arena_allocator.deinit(); - - var err_msg: ErrorMsg = undefined; - // TODO report error - _ = try interpreter.ip.coerce(interpreter.allocator, arena_allocator.allocator(), to_ty, from_val.index, builtin.target, &err_msg); - - return InterpretResult{ .nothing = {} }; - }, - // .@"switch", - // .switch_comma, - // => { - // const cond = data[node_idx].lhs; - // const extra = tree.extraData(data[node_idx].rhs, Ast.Node.SubRange); - // const cases = tree.extra_data[extra.start..extra.end]; - - // for (cases) |case| { - // const switch_case: Ast.full.SwitchCase = switch (tags[case]) { - // .switch_case => tree.switchCase(case), - // .switch_case_one => tree.switchCaseOne(case), - // else => continue, - // }; - // } - // }, - .builtin_call, - .builtin_call_comma, - .builtin_call_two, - .builtin_call_two_comma, - => { - var buffer: [2]Ast.Node.Index = undefined; - const params = ast.builtinCallParams(tree, node_idx, &buffer).?; - const call_name = tree.tokenSlice(main_tokens[node_idx]); - - if (std.mem.eql(u8, call_name, "@compileLog")) { - var final = std.ArrayList(u8).init(interpreter.allocator); - var writer = final.writer(); - try writer.writeAll("log: "); - - for (params, 0..) |param, index| { - const ir_value = (try interpreter.interpret(param, namespace, options)).maybeGetValue() orelse { - try writer.writeAll("indeterminate"); - continue; - }; - const val = ir_value.index; - const ty = interpreter.ip.typeOf(val); - - try writer.print("@as({}, {})", .{ ty.fmt(interpreter.ip), val.fmt(interpreter.ip) }); - if (index != params.len - 1) - try writer.writeAll(", "); - } - try interpreter.recordError(node_idx, "compile_log", "{s}", .{try final.toOwnedSlice()}); - - return InterpretResult{ .nothing = {} }; - } - - if (std.mem.eql(u8, call_name, "@compileError")) { - if (params.len != 0) return error.InvalidBuiltin; - const message = offsets.nodeToSlice(tree, params[0]); - try interpreter.recordError(node_idx, "compile_error", "{s}", .{message}); - return InterpretResult{ .@"return" = {} }; - } - - if (std.mem.eql(u8, call_name, "@import")) { - if (params.len == 0) return error.InvalidBuiltin; - const import_param = params[0]; - if (tags[import_param] != .string_literal) return error.InvalidBuiltin; - - const import_str = tree.tokenSlice(main_tokens[import_param]); - - log.info("Resolving {s} from {s}", .{ import_str[1 .. import_str.len - 1], interpreter.uri }); - - // TODO: Implement root support - if (std.mem.eql(u8, import_str[1 .. import_str.len - 1], "root")) { - const struct_index = try interpreter.ip.createStruct(interpreter.allocator, .{ - .fields = .{}, - .owner_decl = .none, // TODO - .namespace = .none, - .layout = .auto, - .backing_int_ty = .none, - .status = .none, - }); - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .index = try interpreter.ip.get(interpreter.allocator, .{ .unknown_value = .{ - .ty = try interpreter.ip.get(interpreter.allocator, .{ .struct_type = struct_index }), - } }), - } }; - } - - const import_uri = (try interpreter.document_store.uriFromImportStr( - interpreter.allocator, - interpreter.getHandle(), - import_str[1 .. import_str.len - 1], - )) orelse return error.ImportFailure; - defer interpreter.allocator.free(import_uri); - - const import_handle = interpreter.document_store.getOrLoadHandle(import_uri) orelse return error.ImportFailure; - const import_interpreter = try import_handle.getComptimeInterpreter(interpreter.document_store, interpreter.ip); - - if (import_interpreter.mutex.tryLock()) { - defer import_interpreter.mutex.unlock(); - - if (!import_interpreter.has_analyzed_root) { - interpreter.has_analyzed_root = true; - _ = import_interpreter.interpret(0, .none, .{}) catch |err| { - log.err("Failed to interpret file: {s}", .{@errorName(err)}); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - }; - } - } - - return InterpretResult{ - .value = Value{ - .interpreter = import_interpreter, - .node_idx = 0, - .index = .unknown_type, - }, - }; - } - - if (std.mem.eql(u8, call_name, "@TypeOf")) { - if (params.len == 0) return error.InvalidBuiltin; - - const types: []Index = try interpreter.allocator.alloc(Index, params.len); - defer interpreter.allocator.free(types); - - for (params, types) |param, *out_type| { - const value = try (try interpreter.interpret(param, namespace, options)).getValue(); - out_type.* = interpreter.ip.typeOf(value.index); - } - - const peer_type = try interpreter.ip.resolvePeerTypes(interpreter.allocator, types, builtin.target); - - if (peer_type == .none) { - var output = std.ArrayListUnmanaged(u8){}; - var writer = output.writer(interpreter.allocator); - try writer.writeAll("incompatible types: "); - for (types, 0..) |ty, i| { - if (i != 0) try writer.writeAll(", "); - try writer.print("`{}`", .{ty.fmt(interpreter.ip)}); - } - - try interpreter.recordError(node_idx, "invalid_typeof", "{s}", .{output.items}); - return error.InvalidOperation; - } - - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .index = peer_type, - } }; - } - - if (std.mem.eql(u8, call_name, "@hasDecl")) { - if (params.len != 2) return error.InvalidBuiltin; - - const ir_value = try (try interpreter.interpret(params[0], namespace, options)).getValue(); - const field_name = try (try interpreter.interpret(params[1], namespace, options)).getValue(); - _ = field_name; - - const val = ir_value.index; - const ty = interpreter.ip.typeOf(val); - - if (ty != .type_type) return error.InvalidBuiltin; - - const value_namespace = interpreter.ip.getNamespace(ty); - if (value_namespace == .none) return error.InvalidBuiltin; - - return InterpretResult{ .nothing = {} }; - // const name = field_name.index; // TODO - - // const decls = interpreter.namespaces.items(.decls)[@intFromEnum(value_namespace)]; - // const has_decl = decls.contains(name); - - // return InterpretResult{ .value = Value{ - // .interpreter = interpreter, - // .node_idx = node_idx, - // .index = if (has_decl) .bool_true else .bool_false, - // } }; - } - - if (std.mem.eql(u8, call_name, "@as")) { - if (params.len != 2) return error.InvalidBuiltin; - - const as_type = try (try interpreter.interpret(params[0], namespace, options)).getValue(); - // const value = try (try interpreter.interpret(params[1], namespace, options)).getValue(); - - if (interpreter.ip.typeOf(as_type.index) != .type_type) { - return error.InvalidBuiltin; - } - - return InterpretResult{ - .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - // TODO port Sema.coerceExtra to InternPool - .index = try interpreter.ip.get(interpreter.allocator, .{ - .unknown_value = .{ .ty = as_type.index }, - }), - }, - }; - } - - log.err("Builtin not implemented: {s}", .{call_name}); - return error.InvalidBuiltin; - }, - .string_literal => { - const str = tree.getNodeSource(node_idx)[1 .. tree.getNodeSource(node_idx).len - 1]; - - const string_literal_type = try interpreter.ip.get(interpreter.allocator, Key{ .pointer_type = .{ - .elem_type = try interpreter.ip.get(interpreter.allocator, Key{ .array_type = .{ - .child = .u8_type, - .len = @intCast(str.len), - .sentinel = .zero_u8, - } }), - .flags = .{ - .size = .One, - .is_const = true, - }, - } }); - - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .index = try interpreter.ip.getUnknown(interpreter.allocator, string_literal_type), - } }; - }, - // TODO: Add comptime autodetection; e.g. const MyArrayList = std.ArrayList(u8) - .@"comptime" => { - return try interpreter.interpret(data[node_idx].lhs, namespace, .{}); - }, - .fn_proto, - .fn_proto_multi, - .fn_proto_one, - .fn_proto_simple, - .fn_decl, - => { - var buf: [1]Ast.Node.Index = undefined; - const func = tree.fullFnProto(&buf, node_idx).?; - - if (func.name_token == null) return InterpretResult{ .nothing = {} }; - const name = offsets.tokenToSlice(tree, func.name_token.?); - - // TODO: Resolve function type - - const function_type = try interpreter.ip.get(interpreter.allocator, Key{ .function_type = .{ - .args = Index.Slice.empty, - .return_type = Index.none, - } }); - - // var it = func.iterate(&tree); - // while (ast.nextFnParam(&it)) |param| { - // // Add parameter decls - // if (param.name_token) |name_token| { - // // TODO: Think of new method for functions - // if ((try interpreter.interpret(param.type_expr, func_scope_idx, .{ .observe_values = true, .is_comptime = true })).maybeGetValue()) |value| { - // try interpreter.addDeclaration(func_scope_idx, value.value_data.@"type"); - // try fnd.params.append(interpreter.allocator, interpreter.declarations.items.len - 1); - // } else { - // try interpreter.addDeclaration(parent_scope_idx.?, .{ - // .node_idx = node_idx, - // .name = tree.tokenSlice(name_token), - // .scope_idx = func_scope_idx, // orelse std.math.maxInt(usize), - // .@"value" = undefined, - // .@"type" = interpreter.createType(0, .{ .@"anytype" = .{} }), - // }); - // try fnd.params.append(interpreter.allocator, interpreter.declarations.items.len - 1); - // } - // } - // } - - // if ((try interpreter.interpret(func.ast.return_type, func_scope_idx, .{ .observe_values = true, .is_comptime = true })).maybeGetValue()) |value| - // fnd.return_type = value.value_data.@"type"; - - if (namespace != .none) { - const decls = &interpreter.namespaces.items(.decls)[@intFromEnum(namespace)]; - - const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{ - .name = try interpreter.ip.string_pool.getOrPutString(interpreter.allocator, name), - .node_idx = node_idx, - .index = function_type, - .alignment = 0, // TODO - .address_space = .generic, // TODO - .src_namespace = namespace, - .is_pub = false, // TODO - .is_exported = false, // TODO - }); - try decls.putNoClobber(interpreter.allocator, name, decl_index); - } - - return InterpretResult{ .nothing = {} }; - }, - .call, - .call_comma, - .async_call, - .async_call_comma, - .call_one, - .call_one_comma, - .async_call_one, - .async_call_one_comma, - => { - var params: [1]Ast.Node.Index = undefined; - const call_full = tree.fullCall(¶ms, node_idx).?; - - var args = try std.ArrayListUnmanaged(Value).initCapacity(interpreter.allocator, call_full.ast.params.len); - defer args.deinit(interpreter.allocator); - - for (call_full.ast.params) |param| { - args.appendAssumeCapacity(try (try interpreter.interpret(param, namespace, .{})).getValue()); - } - - const func_id_result = try interpreter.interpret(call_full.ast.fn_expr, namespace, .{}); - const func_id_val = try func_id_result.getValue(); - - const call_res = try interpreter.call(namespace, func_id_val.node_idx, args.items, options); - // TODO: Figure out call result memory model; this is actually fine because newScope - // makes this a child of the decl scope which is freed on refresh... in theory - - return switch (call_res.result) { - .value => |v| .{ .value = v }, - .nothing => .{ .nothing = {} }, - }; - }, - .bool_not => { - const result = try interpreter.interpret(data[node_idx].lhs, namespace, .{}); - const ir_value = try result.getValue(); - - const val = ir_value.index; - const ty = interpreter.ip.typeOf(ir_value.index); - - if (ty == .unknown_type) { - return InterpretResult{ .value = .{ - .interpreter = interpreter, - .node_idx = node_idx, - .index = try interpreter.ip.get(interpreter.allocator, .{ - .unknown_value = .{ .ty = .bool_type }, - }), - } }; - } - - if (ty != .bool_type) { - try interpreter.recordError( - node_idx, - "invalid_deref", - "expected type `bool` but got `{}`", - .{ty.fmt(interpreter.ip)}, - ); - return error.InvalidOperation; - } - - std.debug.assert(val == .bool_false or val == .bool_true); - return InterpretResult{ .value = .{ - .interpreter = interpreter, - .node_idx = node_idx, - .index = if (val == .bool_false) .bool_true else .bool_false, - } }; - }, - .address_of => { - // TODO: Make const pointers if we're drawing from a const; - // variables are the only non-const(?) - - const result = try interpreter.interpret(data[node_idx].lhs, namespace, .{}); - const ir_value = try result.getValue(); - - const ty = interpreter.ip.typeOf(ir_value.index); - - const pointer_type = try interpreter.ip.get(interpreter.allocator, Key{ .pointer_type = .{ - .elem_type = ty, - .flags = .{ .size = .One }, - } }); - - return InterpretResult{ .value = .{ - .interpreter = interpreter, - .node_idx = node_idx, - .index = try interpreter.ip.get(interpreter.allocator, .{ - .unknown_value = .{ .ty = pointer_type }, - }), - } }; - }, - .deref => { - const result = try interpreter.interpret(data[node_idx].lhs, namespace, .{}); - const ir_value = (try result.getValue()); - - const ty = interpreter.ip.typeOf(ir_value.index); - - if (ty == .unknown_type) { - return InterpretResult{ .value = .{ - .interpreter = interpreter, - .node_idx = node_idx, - .index = .unknown_unknown, - } }; - } - - const type_key = interpreter.ip.indexToKey(ty); - - if (type_key != .pointer_type) { - try interpreter.recordError(node_idx, "invalid_deref", "cannot deference non-pointer", .{}); - return error.InvalidOperation; - } - - return InterpretResult{ .value = .{ - .interpreter = interpreter, - .node_idx = node_idx, - .index = try interpreter.ip.get(interpreter.allocator, .{ - .unknown_value = .{ .ty = type_key.pointer_type.elem_type }, - }), - } }; - }, - else => { - log.err("Unhandled {any}", .{tags[node_idx]}); - return InterpretResult{ .nothing = {} }; - }, - } -} - -pub const CallResult = struct { - namespace: Namespace.Index, - result: union(enum) { - value: Value, - nothing, - }, -}; - -pub fn call( - interpreter: *ComptimeInterpreter, - namespace: Namespace.Index, - func_node_idx: Ast.Node.Index, - arguments: []const Value, - options: InterpretOptions, -) InterpretError!CallResult { - // _ = options; - - // TODO: type check args - - const tree = interpreter.getHandle().tree; - const node_tags = tree.nodes.items(.tag); - - if (node_tags[func_node_idx] != .fn_decl) return error.CriticalAstFailure; - - var buf: [1]Ast.Node.Index = undefined; - var proto = tree.fullFnProto(&buf, func_node_idx) orelse return error.CriticalAstFailure; - - // TODO: Make argument namespace to evaluate arguments in - try interpreter.namespaces.append(interpreter.allocator, .{ - .parent = namespace, - .node_idx = func_node_idx, - .ty = .none, - }); - const fn_namespace = @as(Namespace.Index, @enumFromInt(interpreter.namespaces.len - 1)); - - var arg_it = proto.iterate(&tree); - var arg_index: usize = 0; - while (ast.nextFnParam(&arg_it)) |param| { - if (arg_index >= arguments.len) return error.MissingArguments; - const tex = try (try interpreter.interpret(param.type_expr, fn_namespace, options)).getValue(); - const tex_ty = interpreter.ip.typeOf(tex.index); - if (tex_ty != .type_type) { - try interpreter.recordError( - param.type_expr, - "expected_type", - "expected type 'type', found '{}'", - .{tex_ty.fmt(interpreter.ip)}, - ); - return error.InvalidCast; - } - // TODO validate that `arguments[arg_index].index`'s types matches tex.index - if (param.name_token) |name_token| { - const name = offsets.tokenToSlice(tree, name_token); - - const decls = &interpreter.namespaces.items(.decls)[@intFromEnum(fn_namespace)]; - const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{ - .name = try interpreter.ip.string_pool.getOrPutString(interpreter.allocator, name), - .node_idx = name_token, - .index = arguments[arg_index].index, - .alignment = 0, // TODO - .address_space = .generic, // TODO - .src_namespace = namespace, - .is_pub = true, // TODO - .is_exported = false, // TODO - }); - try decls.putNoClobber(interpreter.allocator, name, decl_index); - arg_index += 1; - } - } - - const body = tree.nodes.items(.data)[func_node_idx].rhs; - const result = try interpreter.interpret(body, fn_namespace, .{}); - - // TODO: Defers - return CallResult{ - .namespace = fn_namespace, - .result = switch (result) { - .@"return", .nothing => .{ .nothing = {} }, // nothing could be due to an error - .return_with_value => |v| .{ .value = v }, - else => @panic("bruh"), - }, - }; -} diff --git a/src/Config.zig b/src/Config.zig index d3399fe6b..c44faa25b 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -53,9 +53,6 @@ warn_style: bool = false, /// Whether to highlight global var declarations highlight_global_var_declarations: bool = false, -/// Whether to use the comptime interpreter -dangerous_comptime_experiments_do_not_enable: bool = false, - /// When true, skips searching for references in std. Improves lookup speed for functions in user's code. Renaming and go-to-definition will continue to work as is skip_std_references: bool = false, diff --git a/src/DocumentStore.zig b/src/DocumentStore.zig index 7af297e9d..ca4b9a296 100644 --- a/src/DocumentStore.zig +++ b/src/DocumentStore.zig @@ -11,7 +11,6 @@ const BuildAssociatedConfig = @import("BuildAssociatedConfig.zig"); const BuildConfig = @import("build_runner/BuildConfig.zig"); const tracy = @import("tracy"); const translate_c = @import("translate_c.zig"); -const ComptimeInterpreter = @import("ComptimeInterpreter.zig"); const AstGen = std.zig.AstGen; const Zir = std.zig.Zir; const InternPool = @import("analyser/InternPool.zig"); @@ -182,9 +181,6 @@ pub const Handle = struct { /// Contains one entry for every cimport in the document cimports: std.MultiArrayList(CImportHandle) = .{}, - /// error messages from comptime_interpreter or astgen_analyser - analysis_errors: std.ArrayListUnmanaged(ErrorMessage) = .{}, - /// private field impl: struct { /// @bitCast from/to `Status` @@ -197,7 +193,6 @@ pub const Handle = struct { document_scope: DocumentScope = undefined, zir: Zir = undefined, - comptime_interpreter: *ComptimeInterpreter = undefined, associated_build_file: union(enum) { /// The Handle has no associated build file (build.zig). @@ -239,9 +234,7 @@ pub const Handle = struct { /// true if `handle.impl.zir` has been set has_zir: bool = false, zir_outdated: bool = undefined, - /// true if `handle.impl.comptime_interpreter` has been set - has_comptime_interpreter: bool = false, - _: u25 = undefined, + _: u26 = undefined, }; pub const ZirStatus = enum { @@ -283,11 +276,6 @@ pub const Handle = struct { return if (status.zir_outdated) .outdated else .done; } - pub fn getComptimeInterpreter(self: *Handle, document_store: *DocumentStore, ip: *InternPool) error{OutOfMemory}!*ComptimeInterpreter { - if (self.getStatus().has_comptime_interpreter) return self.impl.comptime_interpreter; - return try self.getComptimeInterpreterCold(document_store, ip); - } - /// Returns the associated build file (build.zig) of the handle. /// /// `DocumentStore.build_files` is guaranteed to contain this Uri. @@ -461,43 +449,6 @@ pub const Handle = struct { return self.impl.zir; } - fn getComptimeInterpreterCold( - self: *Handle, - document_store: *DocumentStore, - ip: *InternPool, - ) error{OutOfMemory}!*ComptimeInterpreter { - @setCold(true); - const tracy_zone = tracy.trace(@src()); - defer tracy_zone.end(); - - const comptime_interpreter = try self.impl.allocator.create(ComptimeInterpreter); - errdefer self.impl.allocator.destroy(comptime_interpreter); - - comptime_interpreter.* = ComptimeInterpreter{ - .allocator = self.impl.allocator, - .ip = ip, - .document_store = document_store, - .uri = self.uri, - }; - - { - self.impl.lock.lock(); - errdefer @compileError(""); - - if (self.getStatus().has_comptime_interpreter) { // another thread outpaced us - self.impl.lock.unlock(); - self.impl.allocator.destroy(comptime_interpreter); - return self.impl.comptime_interpreter; - } - self.impl.comptime_interpreter = comptime_interpreter; - const old = self.impl.status.bitSet(@bitOffsetOf(Status, "has_comptime_interpreter"), .release); // atomically set has_imports - std.debug.assert(old == 0); // race condition: another thread set the resource even though we hold the lock - self.impl.lock.unlock(); - } - - return self.impl.comptime_interpreter; - } - fn getStatus(self: Handle) Status { return @bitCast(self.impl.status.load(.acquire)); } @@ -552,18 +503,14 @@ pub const Handle = struct { var old_tree = self.tree; var old_import_uris = self.import_uris; var old_cimports = self.cimports; - var old_analysis_errors = self.analysis_errors; var old_document_scope = if (old_status.has_document_scope) self.impl.document_scope else null; var old_zir = if (old_status.has_zir) self.impl.zir else null; - const old_comptime_interpreter = if (old_status.has_comptime_interpreter) self.impl.comptime_interpreter else null; self.tree = new_tree; self.import_uris = .{}; self.cimports = .{}; - self.analysis_errors = .{}; self.impl.document_scope = undefined; self.impl.zir = undefined; - self.impl.comptime_interpreter = undefined; self.impl.lock.unlock(); @@ -573,18 +520,11 @@ pub const Handle = struct { for (old_import_uris.items) |uri| self.impl.allocator.free(uri); old_import_uris.deinit(self.impl.allocator); - for (old_analysis_errors.items) |err| self.impl.allocator.free(err.message); - old_analysis_errors.deinit(self.impl.allocator); - for (old_cimports.items(.source)) |source| self.impl.allocator.free(source); old_cimports.deinit(self.impl.allocator); if (old_document_scope) |*document_scope| document_scope.deinit(self.impl.allocator); if (old_zir) |*zir| zir.deinit(self.impl.allocator); - if (old_comptime_interpreter) |comptime_interpreter| { - comptime_interpreter.deinit(); - self.impl.allocator.destroy(comptime_interpreter); - } } fn deinit(self: *Handle) void { @@ -595,10 +535,6 @@ pub const Handle = struct { const allocator = self.impl.allocator; - if (status.has_comptime_interpreter) { - self.impl.comptime_interpreter.deinit(); - allocator.destroy(self.impl.comptime_interpreter); - } if (status.has_zir) self.impl.zir.deinit(allocator); if (status.has_document_scope) self.impl.document_scope.deinit(allocator); allocator.free(self.tree.source); @@ -608,9 +544,6 @@ pub const Handle = struct { for (self.import_uris.items) |uri| allocator.free(uri); self.import_uris.deinit(allocator); - for (self.analysis_errors.items) |err| allocator.free(err.message); - self.analysis_errors.deinit(allocator); - for (self.cimports.items(.source)) |source| allocator.free(source); self.cimports.deinit(allocator); diff --git a/src/Server.zig b/src/Server.zig index 0d31ea3b6..78a5307e4 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -19,7 +19,6 @@ const offsets = @import("offsets.zig"); const Ast = std.zig.Ast; const tracy = @import("tracy"); const diff = @import("diff.zig"); -const ComptimeInterpreter = @import("ComptimeInterpreter.zig"); const InternPool = @import("analyser/analyser.zig").InternPool; const known_folders = @import("known-folders"); const BuildRunnerVersion = @import("build_runner/BuildRunnerVersion.zig").BuildRunnerVersion; @@ -332,7 +331,6 @@ fn initAnalyser(server: *Server, handle: ?*DocumentStore.Handle) Analyser { &server.document_store, &server.ip, handle, - server.config.dangerous_comptime_experiments_do_not_enable, ); } @@ -1530,18 +1528,7 @@ fn hoverHandler(server: *Server, arena: std.mem.Allocator, request: types.HoverP var analyser = server.initAnalyser(handle); defer analyser.deinit(); - const response = hover_handler.hover(&analyser, arena, handle, source_index, markup_kind, server.offset_encoding); - - // TODO: Figure out a better solution for comptime interpreter diags - if (server.config.dangerous_comptime_experiments_do_not_enable and - server.client_capabilities.supports_publish_diagnostics) - { - try server.pushJob(.{ - .generate_diagnostics = try server.allocator.dupe(u8, handle.uri), - }); - } - - return response; + return hover_handler.hover(&analyser, arena, handle, source_index, markup_kind, server.offset_encoding); } fn documentSymbolsHandler(server: *Server, arena: std.mem.Allocator, request: types.DocumentSymbolParams) Error!lsp.ResultType("textDocument/documentSymbol") { diff --git a/src/analysis.zig b/src/analysis.zig index 830093b47..c560dae8a 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -15,8 +15,7 @@ const URI = @import("uri.zig"); const log = std.log.scoped(.zls_analysis); const ast = @import("ast.zig"); const tracy = @import("tracy"); -const ComptimeInterpreter = @import("ComptimeInterpreter.zig"); -const InternPool = ComptimeInterpreter.InternPool; +const InternPool = @import("analyser/InternPool.zig"); const references = @import("features/references.zig"); const DocumentScope = @import("DocumentScope.zig"); @@ -38,7 +37,6 @@ collect_callsite_references: bool, resolve_number_literal_values: bool, /// handle of the doc where the request originated root_handle: ?*DocumentStore.Handle, -dangerous_comptime_experiments_do_not_enable: bool, const NodeSet = std.HashMapUnmanaged(NodeWithUri, void, NodeWithUri.Context, std.hash_map.default_max_load_percentage); @@ -47,7 +45,6 @@ pub fn init( store: *DocumentStore, ip: *InternPool, root_handle: ?*DocumentStore.Handle, - dangerous_comptime_experiments_do_not_enable: bool, ) Analyser { return .{ .gpa = gpa, @@ -57,7 +54,6 @@ pub fn init( .collect_callsite_references = true, .resolve_number_literal_values = false, .root_handle = root_handle, - .dangerous_comptime_experiments_do_not_enable = dangerous_comptime_experiments_do_not_enable, }; } @@ -1550,64 +1546,6 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e const body = func_tree.nodes.items(.data)[func_node].rhs; if (try analyser.resolveReturnType(fn_proto, func_handle, if (has_body) body else null)) |ret| { return ret; - } else if (analyser.dangerous_comptime_experiments_do_not_enable) { - // TODO: Better case-by-case; we just use the ComptimeInterpreter when all else fails, - // probably better to use it more liberally - // TODO: Handle non-isolate args; e.g. `const T = u8; TypeFunc(T);` - // var interpreter = ComptimeInterpreter{ .tree = tree, .allocator = arena.allocator() }; - - // var top_decl = try (try interpreter.interpret(0, null, .{})).getValue(); - // var top_scope = interpreter.typeToTypeInfo(top_decl.@"type".info_idx).@"struct".scope; - - // var fn_decl_scope = top_scope.getParentScopeFromNode(node); - - log.info("Invoking interpreter!", .{}); - - const interpreter = try handle.getComptimeInterpreter(analyser.store, analyser.ip); - interpreter.mutex.lock(); - defer interpreter.mutex.unlock(); - - if (!interpreter.has_analyzed_root) { - interpreter.has_analyzed_root = true; - _ = interpreter.interpret(0, .none, .{}) catch |err| { - log.err("Failed to interpret file: {s}", .{@errorName(err)}); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - return null; - }; - } - - const root_namespace: ComptimeInterpreter.Namespace.Index = @enumFromInt(0); - - // TODO: Start from current/nearest-current scope - const result = interpreter.interpret(node, root_namespace, .{}) catch |err| { - log.err("Failed to interpret node: {s}", .{@errorName(err)}); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - return null; - }; - const value = result.getValue() catch |err| { - log.err("interpreter return no result: {s}", .{@errorName(err)}); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - return null; - }; - - return Type{ - .data = .{ - .ip_index = .{ - .node = .{ - .node = value.node_idx, - .handle = node_handle.handle, - }, - .index = value.index, - }, - }, - .is_type_val = analyser.ip.typeOf(value.index) == .type_type, - }; } }, .container_field, diff --git a/src/config_gen/config.json b/src/config_gen/config.json index 216024431..574478739 100644 --- a/src/config_gen/config.json +++ b/src/config_gen/config.json @@ -95,12 +95,6 @@ "type": "bool", "default": false }, - { - "name": "dangerous_comptime_experiments_do_not_enable", - "description": "Whether to use the comptime interpreter", - "type": "bool", - "default": false - }, { "name": "skip_std_references", "description": "When true, skips searching for references in std. Improves lookup speed for functions in user's code. Renaming and go-to-definition will continue to work as is", diff --git a/src/features/diagnostics.zig b/src/features/diagnostics.zig index 5c8370640..21f7cf464 100644 --- a/src/features/diagnostics.zig +++ b/src/features/diagnostics.zig @@ -177,17 +177,6 @@ pub fn generateDiagnostics(server: *Server, arena: std.mem.Allocator, handle: *D } } - try diagnostics.ensureUnusedCapacity(arena, handle.analysis_errors.items.len); - for (handle.analysis_errors.items) |err| { - diagnostics.appendAssumeCapacity(.{ - .range = offsets.locToRange(tree.source, err.loc, server.offset_encoding), - .severity = .Error, - .code = .{ .string = err.code }, - .source = "zls", - .message = err.message, - }); - } - return .{ .uri = handle.uri, .diagnostics = diagnostics.items, diff --git a/src/features/goto.zig b/src/features/goto.zig index 78d61b383..1e0613cd4 100644 --- a/src/features/goto.zig +++ b/src/features/goto.zig @@ -260,7 +260,6 @@ pub fn gotoHandler( &server.document_store, &server.ip, handle, - server.config.dangerous_comptime_experiments_do_not_enable, ); defer analyser.deinit(); diff --git a/src/features/references.zig b/src/features/references.zig index 18225cbfd..ea7f63429 100644 --- a/src/features/references.zig +++ b/src/features/references.zig @@ -447,7 +447,6 @@ pub fn referencesHandler(server: *Server, arena: std.mem.Allocator, request: Gen &server.document_store, &server.ip, handle, - server.config.dangerous_comptime_experiments_do_not_enable, ); defer analyser.deinit(); diff --git a/src/zls.zig b/src/zls.zig index 72243d70d..05b5658f8 100644 --- a/src/zls.zig +++ b/src/zls.zig @@ -14,7 +14,6 @@ pub const lsp = @import("lsp"); pub const types = lsp.types; pub const URI = @import("uri.zig"); pub const DocumentStore = @import("DocumentStore.zig"); -pub const ComptimeInterpreter = @import("ComptimeInterpreter.zig"); pub const diff = @import("diff.zig"); pub const analyser = @import("analyser/analyser.zig"); pub const configuration = @import("configuration.zig"); diff --git a/tests/language_features/comptime_interpreter.zig b/tests/language_features/comptime_interpreter.zig deleted file mode 100644 index ffaefcf65..000000000 --- a/tests/language_features/comptime_interpreter.zig +++ /dev/null @@ -1,436 +0,0 @@ -const std = @import("std"); -const zls = @import("zls"); -const builtin = @import("builtin"); - -const Ast = std.zig.Ast; -const ComptimeInterpreter = zls.ComptimeInterpreter; -const InternPool = zls.analyser.InternPool; -const Index = InternPool.Index; -const Key = InternPool.Key; -const ast = zls.ast; -const offsets = zls.offsets; - -const allocator: std.mem.Allocator = std.testing.allocator; - -test "primitive types" { - try testExpr("true", .{ .simple_value = .bool_true }); - try testExpr("false", .{ .simple_value = .bool_false }); - try testExpr("5", .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 5 } }); - // TODO try testExpr("-2", .{ .int_i64_value = .{ .ty = .comptime_int, .int = -2 } }); - try testExpr("3.0", .{ .float_comptime_value = 3.0 }); - - try testExpr("null", .{ .simple_value = .null_value }); - try testExpr("void", .{ .simple_type = .void }); - try testExpr("undefined", .{ .simple_value = .undefined_value }); - try testExpr("noreturn", .{ .simple_type = .noreturn }); -} - -test "expressions" { - if (true) return error.SkipZigTest; // TODO - try testExpr("5 + 3", .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 8 } }); - // try testExpr("5.2 + 4.2", .{ .simple_type = .comptime_float }, null); - - try testExpr("3 == 3", .{ .simple_valueclear = .bool_true }); - try testExpr("5.2 == 2.1", .{ .simple_value = .bool_false }); - - try testExpr("@as(?bool, null) orelse true", .{ .simple_value = .bool_true }); -} - -test "builtins" { - if (true) return error.SkipZigTest; // TODO - try testExpr("@as(bool, true)", .{ .simple_value = .bool_true }); - try testExpr("@as(u32, 3)", .{ .int_u64_value = .{ .ty = .u32_type, .int = 3 } }); -} - -test "@TypeOf" { - try testExpr("@TypeOf(bool)", .{ .simple_type = .type }); - try testExpr("@TypeOf(5)", .{ .simple_type = .comptime_int }); - try testExpr("@TypeOf(3.14)", .{ .simple_type = .comptime_float }); - - try testExpr("@TypeOf(bool, u32)", .{ .simple_type = .type }); - try testExpr("@TypeOf(true, false)", .{ .simple_type = .bool }); - try testExpr("@TypeOf(3, 2)", .{ .simple_type = .comptime_int }); - try testExpr("@TypeOf(3.14, 2)", .{ .simple_type = .comptime_float }); - - try testExpr("@TypeOf(null, 2)", .{ .optional_type = .{ .payload_type = .comptime_int_type } }); -} - -test "string literal" { - if (true) return error.SkipZigTest; // TODO - var tester = try Tester.init( - \\const foobarbaz = "hello world!"; - \\ - ); - defer tester.deinit(); - const result = try tester.interpret(tester.findVar("foobarbaz")); - - try std.testing.expect(result.ty == .pointer_type); - - try std.testing.expectEqualStrings("hello world!", result.val.?.bytes); -} - -test "labeled block" { - try testExpr( - \\blk: { - \\ break :blk true; - \\} - , .{ .simple_value = .bool_true }); - try testExpr( - \\blk: { - \\ break :blk 3; - \\} - , .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 3 } }); -} - -test "if" { - try testExpr( - \\blk: { - \\ break :blk if (true) true else false; - \\} - , .{ .simple_value = .bool_true }); - try testExpr( - \\blk: { - \\ break :blk if (false) true else false; - \\} - , .{ .simple_value = .bool_false }); - try testExpr( - \\blk: { - \\ if (false) break :blk true; - \\ break :blk false; - \\} - , .{ .simple_value = .bool_false }); - // TODO - // try testExpr( - // \\outer: { - // \\ if (:inner { - // \\ break :inner true; - // \\ }) break :outer true; - // \\ break :outer false; - // \\} - // , .{ .simple_value = .bool_true }); -} - -test "variable lookup" { - try testExpr( - \\blk: { - \\ var foo = 42; - \\ break :blk foo; - \\} - , .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 42 } }); - try testExpr( - \\blk: { - \\ var foo = 1; - \\ var bar = 2; - \\ var baz = 3; - \\ break :blk bar; - \\} - , .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 2 } }); - - var tester = try Tester.init( - \\const bar = foo; - \\const foo = 3; - ); - defer tester.deinit(); - - const result = try tester.interpret(tester.findVar("bar")); - try std.testing.expect(result.val.?.eql(Key{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 3 } }, tester.ip)); -} - -test "field access" { - try testExpr( - \\blk: { - \\ const foo: struct {alpha: u64, beta: bool} = undefined; - \\ break :blk @TypeOf(foo.beta); - \\} - , .{ .simple_type = .bool }); - try testExpr( - \\blk: { - \\ const foo: struct {alpha: u64, beta: bool} = undefined; - \\ break :blk @TypeOf(foo.alpha); - \\} - , .{ .int_type = .{ - .signedness = .unsigned, - .bits = 64, - } }); -} - -test "optional operations" { - if (true) return error.SkipZigTest; // TODO - try testExpr( - \\blk: { - \\ const foo: ?bool = true; - \\ break :blk foo.?; - \\} - , .{ .simple_value = .bool_true }); - try testExpr( - \\blk: { - \\ const foo: ?bool = true; - \\ break :blk foo == null; - \\} - , .{ .simple_value = .bool_false }); -} - -test "pointer operations" { - if (true) return error.SkipZigTest; // TODO - try testExpr( - \\blk: { - \\ const foo: []const u8 = ""; - \\ break :blk foo.len; - \\} - , .{ .int_u64_value = .{ .ty = .usize_type, .int = 0 } }); - try testExpr( - \\blk: { - \\ const foo = true; - \\ break :blk &foo; - \\} - , .{ .simple_value = .bool_true }); - try testExpr( - \\blk: { - \\ const foo = true; - \\ const bar = &foo; - \\ break :blk bar.*; - \\} - , .{ .simple_value = .bool_true }); -} - -test "call return primitive type" { - try testCall( - \\pub fn Foo() type { - \\ return bool; - \\} - , &.{}, .{ .simple_type = .bool }); - - try testCall( - \\pub fn Foo() type { - \\ return u32; - \\} - , &.{}, .{ .int_type = .{ .signedness = .unsigned, .bits = 32 } }); - - try testCall( - \\pub fn Foo() type { - \\ return i128; - \\} - , &.{}, .{ .int_type = .{ .signedness = .signed, .bits = 128 } }); - - try testCall( - \\pub fn Foo() type { - \\ const alpha = i128; - \\ return alpha; - \\} - , &.{}, .{ .int_type = .{ .signedness = .signed, .bits = 128 } }); -} - -test "call return struct" { - var tester = try Tester.init( - \\pub fn Foo() type { - \\ return struct { - \\ slay: bool, - \\ var abc = 123; - \\ }; - \\} - ); - defer tester.deinit(); - const result = try tester.call(tester.findFn("Foo"), &.{}); - - try std.testing.expect(result.ty == .simple_type); - try std.testing.expect(result.ty.simple_type == .type); - const struct_info = tester.ip.getStruct(result.val.?.struct_type); - try std.testing.expectEqual(Index.none, struct_info.backing_int_ty); - try std.testing.expectEqual(std.builtin.Type.ContainerLayout.auto, struct_info.layout); - - try std.testing.expectEqual(@as(usize, 1), struct_info.fields.count()); - try std.testing.expectFmt("slay", "{}", .{tester.ip.fmtId(struct_info.fields.keys()[0])}); - try std.testing.expect(struct_info.fields.values()[0].ty == Index.bool_type); -} - -test "call comptime argument" { - var tester = try Tester.init( - \\pub fn Foo(comptime my_arg: bool) type { - \\ var abc = z: {break :z if (!my_arg) 123 else 0;}; - \\ if (abc == 123) return u69; - \\ return u8; - \\} - ); - defer tester.deinit(); - - const result1 = try tester.call(tester.findFn("Foo"), &.{KV{ - .ty = .{ .simple_type = .bool }, - .val = .{ .simple_value = .bool_true }, - }}); - try std.testing.expect(result1.ty == .simple_type); - try std.testing.expect(result1.ty.simple_type == .type); - try std.testing.expect(result1.val.?.eql(Key{ .int_type = .{ .signedness = .unsigned, .bits = 8 } }, tester.ip)); - - const result2 = try tester.call(tester.findFn("Foo"), &.{KV{ - .ty = .{ .simple_type = .bool }, - .val = .{ .simple_value = .bool_false }, - }}); - try std.testing.expect(result2.ty == .simple_type); - try std.testing.expect(result2.ty.simple_type == .type); - try std.testing.expect(result2.val.?.eql(Key{ .int_type = .{ .signedness = .unsigned, .bits = 69 } }, tester.ip)); -} - -test "call inner function" { - try testCall( - \\pub fn Inner() type { - \\ return bool; - \\} - \\pub fn Foo() type { - \\ return Inner(); - \\} - , &.{}, .{ .simple_type = .bool }); -} - -// -// Helper functions -// - -const KV = struct { - ty: Key, - val: ?Key, -}; - -pub const Tester = struct { - context: Context, - handle: *zls.DocumentStore.Handle, - ip: *zls.analyser.InternPool, - interpreter: *zls.ComptimeInterpreter, - - const Context = @import("../context.zig").Context; - - pub fn init(source: []const u8) !Tester { - var context = try Context.init(); - errdefer context.deinit(); - - const uri = try context.addDocument(source); - const handle = context.server.document_store.getHandle(uri).?; - const interpreter = try handle.getComptimeInterpreter(&context.server.document_store, &context.server.ip); - - // TODO report handle.tree.errors - _ = try interpreter.interpret(0, .none, .{}); - // TODO report handle.analysis_errors - - return .{ - .context = context, - .handle = handle, - .ip = &context.server.ip, - .interpreter = interpreter, - }; - } - - pub fn deinit(self: *Tester) void { - self.context.deinit(); - self.* = undefined; - } - - pub fn call(self: *Tester, func_node: Ast.Node.Index, arguments: []const KV) !KV { - var args = try allocator.alloc(ComptimeInterpreter.Value, arguments.len); - defer allocator.free(args); - - for (arguments, 0..) |argument, i| { - args[i] = .{ - .interpreter = self.interpreter, - .node_idx = 0, - .index = if (argument.val) |val| - try self.ip.get(allocator, val) - else - try self.ip.get(allocator, .{ - .unknown_value = .{ .ty = try self.ip.get(allocator, argument.ty) }, - }), - }; - } - - const namespace: ComptimeInterpreter.Namespace.Index = @enumFromInt(0); // root namespace - const result = (try self.interpreter.call(namespace, func_node, args, .{})).result; - - const val = result.value.index; - const ty = self.ip.typeOf(val); - - return KV{ - .ty = self.ip.indexToKey(ty), - .val = self.ip.indexToKey(val), - }; - } - - pub fn interpret(self: *Tester, node: Ast.Node.Index) !KV { - const namespace: ComptimeInterpreter.Namespace.Index = @enumFromInt(0); // root namespace - const result = try (try self.interpreter.interpret(node, namespace, .{})).getValue(); - - const val = result.index; - const ty = self.ip.typeOf(val); - - return KV{ - .ty = self.ip.indexToKey(ty), - .val = self.ip.indexToKey(val), - }; - } - - pub fn findFn(self: Tester, name: []const u8) Ast.Node.Index { - const handle = self.handle; - for (handle.tree.nodes.items(.tag), 0..) |tag, i| { - if (tag != .fn_decl) continue; - const node: Ast.Node.Index = @intCast(i); - var buffer: [1]Ast.Node.Index = undefined; - const fn_decl = handle.tree.fullFnProto(&buffer, node).?; - const fn_name = offsets.tokenToSlice(handle.tree, fn_decl.name_token.?); - if (std.mem.eql(u8, fn_name, name)) return node; - } - std.debug.panic("failed to find function with name '{s}'", .{name}); - } - - pub fn findVar(self: Tester, name: []const u8) Ast.Node.Index { - const handle = self.handle; - var node: Ast.Node.Index = 0; - while (node < handle.tree.nodes.len) : (node += 1) { - const var_decl = handle.tree.fullVarDecl(node) orelse continue; - const name_token = var_decl.ast.mut_token + 1; - const var_name = offsets.tokenToSlice(handle.tree, name_token); - if (std.mem.eql(u8, var_name, name)) return var_decl.ast.init_node; - } - std.debug.panic("failed to find var declaration with name '{s}'", .{name}); - } -}; - -fn testCall( - source: []const u8, - arguments: []const KV, - expected_ty: Key, -) !void { - var tester = try Tester.init(source); - defer tester.deinit(); - - const result = try tester.call(tester.findFn("Foo"), arguments); - - const ty = try tester.ip.get(allocator, result.ty); - const val = if (result.val) |key| try tester.ip.get(allocator, key) else .none; - const expected_ty_index = try tester.ip.get(allocator, expected_ty); - - try expectEqualIndex(tester.ip, .type_type, ty); - try expectEqualIndex(tester.ip, expected_ty_index, val); -} - -fn testExpr( - expr: []const u8, - expected: Key, -) !void { - const source = try std.fmt.allocPrint(allocator, - \\const foobarbaz = {s}; - , .{expr}); - defer allocator.free(source); - - var tester = try Tester.init(source); - defer tester.deinit(); - - const result = try tester.interpret(tester.findVar("foobarbaz")); - - const expected_index = try tester.ip.get(allocator, expected); - const val = if (result.val) |key| try tester.ip.get(allocator, key) else .none; - - try expectEqualIndex(tester.ip, expected_index, val); -} - -fn expectEqualIndex(ip: *InternPool, expected: Index, actual: Index) !void { - if (expected == actual) return; - std.debug.print("expected `{}`, found `{}`\n", .{ expected.fmtDebug(ip), actual.fmtDebug(ip) }); - return error.TestExpectedEqual; -} diff --git a/tests/tests.zig b/tests/tests.zig index 55437dde9..6469a439d 100644 --- a/tests/tests.zig +++ b/tests/tests.zig @@ -24,5 +24,4 @@ comptime { // Language features _ = @import("language_features/cimport.zig"); - _ = @import("language_features/comptime_interpreter.zig"); }