zls/tests/language_features/comptime_interpreter.zig

274 lines
8.8 KiB
Zig
Raw Normal View History

2022-10-28 04:59:24 +01:00
const std = @import("std");
const zls = @import("zls");
2023-01-14 13:08:15 +00:00
const builtin = @import("builtin");
2022-10-28 04:59:24 +01:00
const Ast = std.zig.Ast;
const ComptimeInterpreter = zls.ComptimeInterpreter;
2023-01-14 13:08:15 +00:00
const InternPool = zls.InternPool;
const Index = InternPool.Index;
const Key = InternPool.Key;
2022-10-28 04:59:24 +01:00
const allocator: std.mem.Allocator = std.testing.allocator;
2023-01-20 16:06:16 +00:00
test "ComptimeInterpreter - primitive types" {
try testExprCheck("true", .{ .simple = .bool }, .{ .simple = .bool_true });
try testExprCheck("false", .{ .simple = .bool }, .{ .simple = .bool_false });
try testExprCheck("5", .{ .simple = .comptime_int }, .{ .int_u64_value = 5 });
// TODO try testExprCheck("-2", .{ .simple = .comptime_int }, .{ .int_i64_value = -2 });
try testExprCheck("3.0", .{ .simple = .comptime_float }, null);
if (true) return error.SkipZigTest; // TODO
try testExprCheck("null", .{ .simple = .null_type }, .{ .simple = .null_value });
try testExprCheck("void", .{ .simple = .void }, .{ .simple = .void_value });
try testExprCheck("undefined", .{ .simple = .undefined_type }, .{ .simple = .undefined_value });
try testExprCheck("noreturn", .{ .simple = .noreturn }, .{ .simple = .unreachable_value });
}
test "ComptimeInterpreter - expressions" {
if (true) return error.SkipZigTest; // TODO
try testExprCheck("5 + 3", .{ .simple = .comptime_int }, .{ .int_u64_value = 8 });
try testExprCheck("5.2 + 4.2", .{ .simple = .comptime_float }, null);
try testExprCheck("3 == 3", .{ .simple = .bool }, .{ .simple = .bool_true });
try testExprCheck("5.2 == 2.1", .{ .simple = .bool }, .{ .simple = .bool_false });
try testExprCheck("@as(?bool, null) orelse true", .{ .simple = .bool }, .{ .simple = .bool_true });
}
test "ComptimeInterpreter - builtins" {
if (true) return error.SkipZigTest; // TODO
try testExprCheck("@as(bool, true)", .{ .simple = .bool }, .{ .simple = .bool_true });
try testExprCheck("@as(u32, 3)", .{ .int_type = .{
.signedness = .unsigned,
.bits = 32,
} }, .{ .int_u64_value = 3 });
}
2023-01-14 13:08:15 +00:00
test "ComptimeInterpreter - call return primitive type" {
try testCallCheck(
\\pub fn Foo() type {
\\ return bool;
\\}
, &.{}, .{ .simple = .bool });
2022-11-11 01:51:02 +00:00
2023-01-14 13:08:15 +00:00
try testCallCheck(
\\pub fn Foo() type {
\\ return u32;
2022-10-28 04:59:24 +01:00
\\}
2023-01-14 13:08:15 +00:00
, &.{}, .{ .int_type = .{ .signedness = .unsigned, .bits = 32 } });
2022-10-28 04:59:24 +01:00
2023-01-14 13:08:15 +00:00
try testCallCheck(
\\pub fn Foo() type {
\\ return i128;
\\}
, &.{}, .{ .int_type = .{ .signedness = .signed, .bits = 128 } });
2022-10-28 04:59:24 +01:00
2023-01-14 13:08:15 +00:00
try testCallCheck(
\\pub fn Foo() type {
\\ const alpha = i128;
\\ return alpha;
\\}
, &.{}, .{ .int_type = .{ .signedness = .signed, .bits = 128 } });
}
test "ComptimeInterpreter - call return struct" {
var result = try testCall(
\\pub fn Foo() type {
\\ return struct {
\\ slay: bool,
\\ var abc = 123;
\\ };
\\}
, &.{});
defer result.deinit();
2023-01-20 16:06:16 +00:00
try std.testing.expect(result.ty == .simple);
try std.testing.expect(result.ty.simple == .type);
const struct_info = result.val.struct_type;
2023-01-14 13:08:15 +00:00
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.len);
// try std.testing.expectEqualStrings("slay", struct_info.fields[0].name);
// try std.testing.expect(struct_info.fields[0].ty != .none); // TODO check for bool
}
2023-01-04 10:12:29 +00:00
2023-01-14 13:08:15 +00:00
test "ComptimeInterpreter - call comptime argument" {
const source =
\\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;
\\}
;
var result1 = try testCall(source, &.{
Value{
.ty = .{ .simple = .bool },
.val = .{ .simple = .bool_true },
},
});
defer result1.deinit();
2023-01-20 16:06:16 +00:00
try std.testing.expect(result1.ty == .simple);
try std.testing.expect(result1.ty.simple == .type);
try std.testing.expectEqual(Key{ .int_type = .{ .signedness = .unsigned, .bits = 8 } }, result1.val);
2023-01-14 13:08:15 +00:00
var result2 = try testCall(source, &.{
Value{
.ty = .{ .simple = .bool },
.val = .{ .simple = .bool_false },
},
});
defer result2.deinit();
2023-01-20 16:06:16 +00:00
try std.testing.expect(result2.ty == .simple);
try std.testing.expect(result2.ty.simple == .type);
try std.testing.expectEqual(Key{ .int_type = .{ .signedness = .unsigned, .bits = 69 } }, result2.val);
2023-01-14 13:08:15 +00:00
}
2022-11-11 01:51:02 +00:00
2023-01-14 13:08:15 +00:00
//
// Helper functions
//
2023-01-20 16:06:16 +00:00
const Result = struct {
2023-01-14 13:08:15 +00:00
interpreter: ComptimeInterpreter,
2023-01-20 16:06:16 +00:00
ty: Key,
val: Key,
2022-11-11 01:51:02 +00:00
2023-01-20 16:06:16 +00:00
pub fn deinit(self: *Result) void {
2023-01-14 13:08:15 +00:00
self.interpreter.deinit();
}
};
2022-10-28 04:59:24 +01:00
2023-01-14 13:08:15 +00:00
const Value = struct {
ty: Key,
val: Key,
};
2022-10-28 06:22:03 +01:00
2023-01-20 16:06:16 +00:00
fn testCall(source: []const u8, arguments: []const Value) !Result {
2022-11-11 01:51:02 +00:00
var config = zls.Config{};
var doc_store = zls.DocumentStore{
.allocator = allocator,
.config = &config,
};
defer doc_store.deinit();
2023-01-14 13:08:15 +00:00
const test_uri: []const u8 = switch (builtin.os.tag) {
.windows => "file:///C:\\test.zig",
else => "file:///test.zig",
};
const handle = try doc_store.openDocument(test_uri, source);
2022-10-28 06:22:03 +01:00
2022-11-11 01:51:02 +00:00
var interpreter = ComptimeInterpreter{
.allocator = allocator,
.document_store = &doc_store,
2023-01-14 13:08:15 +00:00
.uri = handle.uri,
2022-11-11 01:51:02 +00:00
};
2023-01-14 13:08:15 +00:00
errdefer interpreter.deinit();
_ = try interpreter.interpret(0, .none, .{});
2022-10-28 06:22:03 +01:00
2023-01-14 13:08:15 +00:00
var args = try allocator.alloc(ComptimeInterpreter.Value, arguments.len);
defer allocator.free(args);
2022-11-11 01:51:02 +00:00
2023-01-14 13:08:15 +00:00
for (arguments) |argument, i| {
args[i] = .{
.interpreter = &interpreter,
.node_idx = 0,
.ty = try interpreter.ip.get(interpreter.allocator, argument.ty),
.val = try interpreter.ip.get(interpreter.allocator, argument.val),
};
}
2022-11-11 01:51:02 +00:00
2023-01-14 13:08:15 +00:00
const func_node = for (handle.tree.nodes.items(.tag)) |tag, i| {
if (tag == .fn_decl) break @intCast(Ast.Node.Index, i);
} else unreachable;
const call_result = try interpreter.call(.none, func_node, args, .{});
2023-01-20 16:06:16 +00:00
return Result{
2023-01-14 13:08:15 +00:00
.interpreter = interpreter,
2023-01-20 16:06:16 +00:00
.ty = interpreter.ip.indexToKey(call_result.result.value.ty),
.val = interpreter.ip.indexToKey(call_result.result.value.val),
2023-01-14 13:08:15 +00:00
};
}
2022-10-28 06:22:03 +01:00
2023-01-14 13:08:15 +00:00
fn testCallCheck(
source: []const u8,
arguments: []const Value,
2023-01-20 16:06:16 +00:00
expected_ty: Key,
2023-01-14 13:08:15 +00:00
) !void {
var result = try testCall(source, arguments);
defer result.deinit();
2023-01-20 16:06:16 +00:00
try std.testing.expect(result.ty == .simple);
try std.testing.expect(result.ty.simple == .type);
if (!expected_ty.eql(result.val)) {
std.debug.print("expected type `{}`, found `{}`\n", .{ expected_ty.fmtType(result.interpreter.ip), result.val.fmtType(result.interpreter.ip) });
return error.TestExpectedEqual;
}
}
fn testInterpret(source: []const u8, node_idx: Ast.Node.Index) !Result {
var config = zls.Config{};
var doc_store = zls.DocumentStore{
.allocator = allocator,
.config = &config,
};
defer doc_store.deinit();
const test_uri: []const u8 = switch (builtin.os.tag) {
.windows => "file:///C:\\test.zig",
else => "file:///test.zig",
};
const handle = try doc_store.openDocument(test_uri, source);
var interpreter = ComptimeInterpreter{
.allocator = allocator,
.document_store = &doc_store,
.uri = handle.uri,
};
errdefer interpreter.deinit();
_ = try interpreter.interpret(0, .none, .{});
const result = try interpreter.interpret(node_idx, .none, .{});
return Result{
.interpreter = interpreter,
.ty = interpreter.ip.indexToKey(result.value.ty),
.val = interpreter.ip.indexToKey(result.value.val),
};
}
fn testExprCheck(
expr: []const u8,
expected_ty: Key,
expected_val: ?Key,
) !void {
const source = try std.fmt.allocPrint(allocator,
\\const foobarbaz = {s};
, .{expr});
defer allocator.free(source);
var result = try testInterpret(source, 1);
defer result.deinit();
var ip: *InternPool = &result.interpreter.ip;
if (!expected_ty.eql(result.ty)) {
std.debug.print("expected type `{}`, found `{}`\n", .{ expected_ty.fmtType(ip.*), result.ty.fmtType(ip.*) });
return error.TestExpectedEqual;
}
if (expected_val) |expected_value| {
if (!expected_value.eql(result.val)) {
const expected_ty_index = try ip.get(allocator, expected_ty);
const actual_ty_index = try ip.get(allocator, result.ty);
std.debug.print("expected value `{}`, found `{}`\n", .{
expected_value.fmtValue(expected_ty_index, ip.*),
result.val.fmtValue(actual_ty_index, ip.*),
});
return error.TestExpectedEqual;
}
}
2022-10-28 06:22:03 +01:00
}