diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index 585e3ff..26d8c37 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -180,6 +180,9 @@ pub const ValueData = union(enum) { signed_int: i64, float: f64, + runtime, + comptime_undetermined, + pub fn eql(data: ValueData, other_data: ValueData) bool { if (std.meta.activeTag(data) != std.meta.activeTag(other_data)) return false; // std.enums. @@ -190,7 +193,8 @@ pub const ValueData = union(enum) { .unsigned_int => return data.unsigned_int == other_data.unsigned_int, .signed_int => return data.signed_int == other_data.signed_int, .float => return data.float == other_data.float, - else => @panic("Simple eql not implemented!"), + + else => return false, } } }; @@ -395,6 +399,7 @@ pub const InterpretResult = union(enum) { return switch (result) { .break_with_value => |v| v.value, .value => |v| v, + .return_with_value => |v| v, else => null, }; } @@ -414,6 +419,7 @@ pub const InterpretError = std.mem.Allocator.Error || std.fmt.ParseIntError || s InvalidOperation, CriticalAstFailure, InvalidBuiltin, + IdentifierNotFound, }; pub fn interpret( interpreter: *ComptimeInterpreter, @@ -623,7 +629,7 @@ pub fn interpret( } std.log.err("Identifier not found: {s}", .{value}); - @panic("Could not find identifier"); + return error.IdentifierNotFound; }, .grouped_expression => { return try interpreter.interpret(data[node_idx].lhs, scope, options); @@ -733,34 +739,17 @@ pub fn interpret( => { var buffer: [2]Ast.Node.Index = undefined; const params = ast.builtinCallParams(tree, node_idx, &buffer).?; + _ = params; const call_name = tree.tokenSlice(main_tokens[node_idx]); if (std.mem.eql(u8, call_name, "@compileLog")) { - pp: for (params) |param| { - const res = try (try interpreter.interpret(param, scope, .{})).getValue(); - const ti = interpreter.type_info.items[res.@"type".info_idx]; - switch (ti) { - .pointer => |ptr| { - const child = interpreter.type_info.items[ptr.child.info_idx]; - if (ptr.size == .slice and child == .int and child.int.bits == 8 and child.int.signedness == .unsigned) { - - // TODO: Fix once I optimize slices - std.debug.print("@compileLog output: ", .{}); - for (res.value_data.slice_ptr.items) |i| std.debug.print("{c}", .{@truncate(u8, i.unsigned_int)}); - std.debug.print("\n", .{}); - - break :pp; - } - }, - else => {}, - } - - @panic("compileLog argument type not printable!"); - } - return InterpretResult{ .nothing = .{} }; } + if (std.mem.eql(u8, call_name, "@compileError")) { + return InterpretResult{ .@"return" = .{} }; + } + std.log.info("Builtin not implemented: {s}", .{call_name}); @panic("Builtin not implemented"); // return error.InvalidBuiltin; @@ -844,12 +833,13 @@ pub fn interpret( // .value_data = .{ .@"fn" = .{} }, // }; - // const name = ast.getDeclName(tree, node_idx).?; + // const name = analysis.getDeclName(tree, node_idx).?; + // // TODO: DANGER DANGER DANGER // try scope.?.declarations.put(interpreter.allocator, name, .{ // .node_idx = node_idx, // .name = name, - // .@"type" = value.@"type", - // .@"value" = value, + // .@"type" = undefined, + // .@"value" = undefined, // }); return InterpretResult{ .nothing = .{} }; @@ -863,60 +853,28 @@ pub fn interpret( .async_call_one, .async_call_one_comma, => { - // var params: [1]Ast.Node.Index = undefined; - // const call = ast.callFull(tree, node_idx, ¶ms) orelse unreachable; + var params: [1]Ast.Node.Index = undefined; + const call_full = ast.callFull(tree, node_idx, ¶ms) orelse unreachable; - // const callee = .{ .node = call.ast.fn_expr, .handle = handle }; - // const decl = (try resolveTypeOfNodeInternal(store, arena, callee, bound_type_params)) orelse - // return null; + var args = try std.ArrayListUnmanaged(Value).initCapacity(interpreter.allocator, call_full.ast.params.len); + defer args.deinit(interpreter.allocator); - // if (decl.type.is_type_val) return null; - // const decl_node = switch (decl.type.data) { - // .other => |n| n, - // else => return null, - // }; - // var buf: [1]Ast.Node.Index = undefined; - // const func_maybe = ast.fnProto(decl.handle.tree, decl_node, &buf); + for (call_full.ast.params) |param| { + try args.append(interpreter.allocator, try (try interpreter.interpret(param, scope, .{})).getValue()); + } - // if (func_maybe) |fn_decl| { - // var expected_params = fn_decl.ast.params.len; - // // If we call as method, the first parameter should be skipped - // // TODO: Back-parse to extract the self argument? - // var it = fn_decl.iterate(&decl.handle.tree); - // if (token_tags[call.ast.lparen - 2] == .period) { - // if (try hasSelfParam(arena, store, decl.handle, fn_decl)) { - // _ = ast.nextFnParam(&it); - // expected_params -= 1; - // } - // } + // TODO: Make this actually resolve function; requires interpreting whole file + // const res = try interpreter.interpret(call_full.ast.fn_expr, scope, .{}); + // const value = try res.getValue(); - // // Bind type params to the arguments passed in the call. - // const param_len = std.math.min(call.ast.params.len, expected_params); - // var i: usize = 0; - // while (ast.nextFnParam(&it)) |decl_param| : (i += 1) { - // if (i >= param_len) break; - // if (!isMetaType(decl.handle.tree, decl_param.type_expr)) - // continue; + const call_res = try interpreter.call(tree.rootDecls()[0], args.items, options); + // defer call_res.scope.deinit(); + // TODO: Figure out call result memory model - // const argument = .{ .node = call.ast.params[i], .handle = handle }; - // const argument_type = (try resolveTypeOfNodeInternal( - // store, - // arena, - // argument, - // bound_type_params, - // )) orelse - // continue; - // if (!argument_type.type.is_type_val) continue; - - // try bound_type_params.put(arena.allocator(), decl_param, argument_type); - // } - - // const has_body = decl.handle.tree.nodes.items(.tag)[decl_node] == .fn_decl; - // const body = decl.handle.tree.nodes.items(.data)[decl_node].rhs; - // return try resolveReturnType(store, arena, fn_decl, decl.handle, bound_type_params, if (has_body) body else null); - // } - // return null; - return InterpretResult{ .nothing = .{} }; + return switch (call_res.result) { + .value => |v| .{ .value = v }, + .nothing => .{ .nothing = {} }, + }; }, .bool_not => { const result = try interpreter.interpret(data[node_idx].lhs, scope, .{}); @@ -955,16 +913,33 @@ pub fn call( // TODO: Arguments _ = options; - _ = arguments; + // _ = arguments; const tree = interpreter.tree; const tags = tree.nodes.items(.tag); std.debug.assert(tags[func_node_idx] == .fn_decl); - // TODO: Parent sc]ope exploration (consts, typefuncs, etc.) var fn_scope = try interpreter.newScope(null, func_node_idx); + var buf: [1]Ast.Node.Index = undefined; + var proto = ast.fnProto(tree, func_node_idx, &buf).?; + + var arg_it = proto.iterate(&tree); + var arg_index: usize = 0; + while (ast.nextFnParam(&arg_it)) |param| { + if (param.name_token) |nt| { + const decl = Declaration{ + .node_idx = param.type_expr, + .name = tree.tokenSlice(nt), + .@"type" = arguments[arg_index].@"type", + .value = arguments[arg_index], + }; + try fn_scope.declarations.put(interpreter.allocator, tree.tokenSlice(nt), decl); + arg_index += 1; + } + } + const body = tree.nodes.items(.data)[func_node_idx].rhs; const result = try interpreter.interpret(body, fn_scope, .{}); diff --git a/src/Server.zig b/src/Server.zig index 1cb2f68..d563ede 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -484,6 +484,23 @@ fn typeToCompletion( null, ), .primitive, .array_index => {}, + .@"comptime" => |co| { + const ti = co.interpreter.typeToTypeInfo(co.type); + switch (ti) { + .@"struct" => |st| { + var it = st.scope.declarations.iterator(); + while (it.next()) |entry| { + try list.append(allocator, .{ + .label = entry.key_ptr.*, + .kind = if (entry.value_ptr.isConstant(co.interpreter.tree)) .Constant else .Variable, + .insertText = entry.key_ptr.*, + .insertTextFormat = .PlainText, + }); + } + }, + else => {}, + } + }, } } @@ -2663,8 +2680,7 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void if (s.len == 0) { if (field.field_type == ?[]const u8) { break :blk null; - } - else { + } else { break :blk s; } } @@ -2727,35 +2743,7 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void } } - const method_map = .{ - .{ "initialized", void, initializedHandler }, - .{"$/cancelRequest"}, - .{"textDocument/willSave"}, - .{ "initialize", requests.Initialize, initializeHandler }, - .{ "shutdown", void, shutdownHandler }, - .{ "exit", void, exitHandler }, - .{ "textDocument/didOpen", requests.OpenDocument, openDocumentHandler }, - .{ "textDocument/didChange", requests.ChangeDocument, changeDocumentHandler }, - .{ "textDocument/didSave", requests.SaveDocument, saveDocumentHandler }, - .{ "textDocument/didClose", requests.CloseDocument, closeDocumentHandler }, - .{ "textDocument/semanticTokens/full", requests.SemanticTokensFull, semanticTokensFullHandler }, - .{ "textDocument/inlayHint", requests.InlayHint, inlayHintHandler }, - .{ "textDocument/completion", requests.Completion, completionHandler }, - .{ "textDocument/signatureHelp", requests.SignatureHelp, signatureHelpHandler }, - .{ "textDocument/definition", requests.GotoDefinition, gotoDefinitionHandler }, - .{ "textDocument/typeDefinition", requests.GotoDefinition, gotoDefinitionHandler }, - .{ "textDocument/implementation", requests.GotoDefinition, gotoDefinitionHandler }, - .{ "textDocument/declaration", requests.GotoDeclaration, gotoDeclarationHandler }, - .{ "textDocument/hover", requests.Hover, hoverHandler }, - .{ "textDocument/documentSymbol", requests.DocumentSymbols, documentSymbolsHandler }, - .{ "textDocument/formatting", requests.Formatting, formattingHandler }, - .{ "textDocument/rename", requests.Rename, renameHandler }, - .{ "textDocument/references", requests.References, referencesHandler }, - .{ "textDocument/documentHighlight", requests.DocumentHighlight, documentHighlightHandler }, - .{ "textDocument/codeAction", requests.CodeAction, codeActionHandler }, - .{ "workspace/didChangeConfiguration", Config.DidChangeConfigurationParams, didChangeConfigurationHandler }, - .{ "textDocument/foldingRange", requests.FoldingRange, foldingRangeHandler }, - }; + const method_map = .{ .{ "initialized", void, initializedHandler }, .{"$/cancelRequest"}, .{"textDocument/willSave"}, .{ "initialize", requests.Initialize, initializeHandler }, .{ "shutdown", void, shutdownHandler }, .{ "exit", void, exitHandler }, .{ "textDocument/didOpen", requests.OpenDocument, openDocumentHandler }, .{ "textDocument/didChange", requests.ChangeDocument, changeDocumentHandler }, .{ "textDocument/didSave", requests.SaveDocument, saveDocumentHandler }, .{ "textDocument/didClose", requests.CloseDocument, closeDocumentHandler }, .{ "textDocument/semanticTokens/full", requests.SemanticTokensFull, semanticTokensFullHandler }, .{ "textDocument/inlayHint", requests.InlayHint, inlayHintHandler }, .{ "textDocument/completion", requests.Completion, completionHandler }, .{ "textDocument/signatureHelp", requests.SignatureHelp, signatureHelpHandler }, .{ "textDocument/definition", requests.GotoDefinition, gotoDefinitionHandler }, .{ "textDocument/typeDefinition", requests.GotoDefinition, gotoDefinitionHandler }, .{ "textDocument/implementation", requests.GotoDefinition, gotoDefinitionHandler }, .{ "textDocument/declaration", requests.GotoDeclaration, gotoDeclarationHandler }, .{ "textDocument/hover", requests.Hover, hoverHandler }, .{ "textDocument/documentSymbol", requests.DocumentSymbols, documentSymbolsHandler }, .{ "textDocument/formatting", requests.Formatting, formattingHandler }, .{ "textDocument/rename", requests.Rename, renameHandler }, .{ "textDocument/references", requests.References, referencesHandler }, .{ "textDocument/documentHighlight", requests.DocumentHighlight, documentHighlightHandler }, .{ "textDocument/codeAction", requests.CodeAction, codeActionHandler }, .{ "workspace/didChangeConfiguration", Config.DidChangeConfigurationParams, didChangeConfigurationHandler }, .{ "textDocument/foldingRange", requests.FoldingRange, foldingRangeHandler } }; if (zig_builtin.zig_backend == .stage1) { // Hack to avoid `return`ing in the inline for, which causes bugs. diff --git a/src/analysis.zig b/src/analysis.zig index 2e35540..e9a1143 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -5,6 +5,7 @@ const types = @import("types.zig"); const offsets = @import("offsets.zig"); const log = std.log.scoped(.analysis); const ast = @import("ast.zig"); +const ComptimeInterpreter = @import("ComptimeInterpreter.zig"); var using_trail: std.ArrayList([*]const u8) = undefined; var resolve_trail: std.ArrayList(NodeWithHandle) = undefined; @@ -491,7 +492,7 @@ fn resolveUnwrapErrorType(store: *DocumentStore, arena: *std.heap.ArenaAllocator .type = .{ .data = .{ .other = n }, .is_type_val = rhs.type.is_type_val }, .handle = rhs.handle, }, - .primitive, .slice, .pointer, .array_index => return null, + .primitive, .slice, .pointer, .array_index, .@"comptime" => return null, }; if (rhs.handle.tree.nodes.items(.tag)[rhs_node] == .error_union) { @@ -742,7 +743,37 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, arena: *std.heap.ArenaAl const has_body = decl.handle.tree.nodes.items(.tag)[decl_node] == .fn_decl; const body = decl.handle.tree.nodes.items(.data)[decl_node].rhs; - return try resolveReturnType(store, arena, fn_decl, decl.handle, bound_type_params, if (has_body) body else null); + if (try resolveReturnType(store, arena, fn_decl, decl.handle, bound_type_params, if (has_body) body else null)) |ret| { + return ret; + } else { + // 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() }; + + const result = interpreter.interpret(node, null, .{}) catch |err| { + std.log.err("Interpreter error: {s}", .{@errorName(err)}); + return null; + }; + const val = result.getValue() catch |err| { + std.log.err("Interpreter error: {s}", .{@errorName(err)}); + return null; + }; + + const ti = interpreter.typeToTypeInfo(val.@"type"); + if (ti != .@"type") { + std.log.err("Not a type: { }", .{interpreter.formatTypeInfo(ti)}); + return null; + } + + return TypeWithHandle{ + .type = .{ + .data = .{ .@"comptime" = .{ .interpreter = interpreter, .type = val.value_data.@"type" } }, + .is_type_val = true, + }, + .handle = node_handle.handle, + }; + } } return null; }, @@ -965,6 +996,10 @@ pub const Type = struct { other: Ast.Node.Index, primitive: Ast.Node.Index, array_index, + @"comptime": struct { + interpreter: ComptimeInterpreter, + type: ComptimeInterpreter.Type, + }, }, /// If true, the type `type`, the attached data is the value of the type value. is_type_val: bool, diff --git a/tests/language_features/comptime_interpreter.zig b/tests/language_features/comptime_interpreter.zig index 9a7e3ef..3d16ee3 100644 --- a/tests/language_features/comptime_interpreter.zig +++ b/tests/language_features/comptime_interpreter.zig @@ -9,8 +9,8 @@ const allocator: std.mem.Allocator = std.testing.allocator; test "ComptimeInterpreter - basic test" { var tree = try std.zig.parse(allocator, - \\pub fn ReturnMyType() type { - \\ var abc = z: {break :z if (!false) 123 else 0;}; + \\pub fn ReturnMyType(comptime my_arg: bool) type { + \\ var abc = z: {break :z if (!my_arg) 123 else 0;}; \\ if (abc == 123) return u69; \\ return u8; \\} @@ -20,10 +20,29 @@ test "ComptimeInterpreter - basic test" { var interpreter = ComptimeInterpreter{ .tree = tree, .allocator = allocator }; defer interpreter.deinit(); - const z = try interpreter.call(tree.rootDecls()[0], &.{}, .{}); - defer z.scope.deinit(); + var bool_type = try interpreter.createType(std.math.maxInt(std.zig.Ast.Node.Index), .{ .@"bool" = .{} }); + var arg_false = ComptimeInterpreter.Value{ + .node_idx = std.math.maxInt(std.zig.Ast.Node.Index), + .@"type" = bool_type, + .value_data = .{ .@"bool" = false }, + }; + var arg_true = ComptimeInterpreter.Value{ + .node_idx = std.math.maxInt(std.zig.Ast.Node.Index), + .@"type" = bool_type, + .value_data = .{ .@"bool" = true }, + }; - try std.testing.expectFmt("u69", "{any}", .{interpreter.formatTypeInfo(interpreter.typeToTypeInfo(z.result.value.value_data.@"type"))}); + const call_with_false = try interpreter.call(tree.rootDecls()[0], &.{ + arg_false, + }, .{}); + defer call_with_false.scope.deinit(); + const call_with_true = try interpreter.call(tree.rootDecls()[0], &.{ + arg_true, + }, .{}); + defer call_with_true.scope.deinit(); + + try std.testing.expectFmt("u69", "{any}", .{interpreter.formatTypeInfo(interpreter.typeToTypeInfo(call_with_false.result.value.value_data.@"type"))}); + try std.testing.expectFmt("u8", "{any}", .{interpreter.formatTypeInfo(interpreter.typeToTypeInfo(call_with_true.result.value.value_data.@"type"))}); } test "ComptimeInterpreter - struct" {