InternPool: replace untyped values with typed values (#1023)

* InternPool: replace untyped values with typed values

* InternPool: remove `indexToTag`

* InternPool: rework representation of optional values

* add representation for unknown values and types

* ComptimeInterpreter: use InternPool typed-values

* ComptimeInterpreter: field access test

* ComptimeInterpreter: improve handling of if expressions

* InternPool: fix typeOf on a comptime float

* ComptimeInterpreter: implement TypeOf with multiple parameters
This commit is contained in:
Techatrix 2023-02-27 22:53:46 +00:00 committed by GitHub
parent 89ab9fdf70
commit 30869d7d87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 707 additions and 605 deletions

View File

@ -70,19 +70,12 @@ pub fn deinit(interpreter: *ComptimeInterpreter) void {
interpreter.namespaces.deinit(interpreter.allocator); interpreter.namespaces.deinit(interpreter.allocator);
} }
pub const Type = struct {
interpreter: *ComptimeInterpreter,
node_idx: Ast.Node.Index,
ty: Index,
};
pub const Value = struct { pub const Value = struct {
interpreter: *ComptimeInterpreter, interpreter: *ComptimeInterpreter,
node_idx: Ast.Node.Index, node_idx: Ast.Node.Index,
ty: Index, /// this stores both the type and the value
val: Index, index: Index,
}; };
// pub const Comptimeness = enum { @"comptime", runtime }; // pub const Comptimeness = enum { @"comptime", runtime };
@ -234,19 +227,20 @@ pub fn interpret(
continue; continue;
}; };
var init_type_value = try (try interpreter.interpret(container_field.ast.type_expr, container_namespace, .{})).getValue(); var init_value = try (try interpreter.interpret(container_field.ast.type_expr, container_namespace, .{})).getValue();
var default_value = if (container_field.ast.value_expr == 0) var default_value = if (container_field.ast.value_expr == 0)
Index.none Index.none
else else
(try (try interpreter.interpret(container_field.ast.value_expr, container_namespace, .{})).getValue()).val; // TODO check ty (try (try interpreter.interpret(container_field.ast.value_expr, container_namespace, .{})).getValue()).index; // TODO check ty
if (init_type_value.ty != Index.type_type) { const init_value_ty = interpreter.ip.indexToKey(init_value.index).typeOf();
if (init_value_ty != .type_type) {
try interpreter.recordError( try interpreter.recordError(
container_field.ast.type_expr, container_field.ast.type_expr,
"expected_type", "expected_type",
"expected type 'type', found '{}'", "expected type 'type', found '{}'",
.{init_type_value.ty.fmtType(interpreter.ip)}, .{init_value_ty.fmt(interpreter.ip)},
); );
continue; continue;
} }
@ -254,7 +248,7 @@ pub fn interpret(
const field_name = tree.tokenSlice(container_field.ast.main_token); const field_name = tree.tokenSlice(container_field.ast.main_token);
try struct_info.fields.put(interpreter.allocator, field_name, .{ try struct_info.fields.put(interpreter.allocator, field_name, .{
.ty = init_type_value.val, .ty = init_value.index,
.default_value = default_value, .default_value = default_value,
.alignment = 0, // TODO, .alignment = 0, // TODO,
.is_comptime = false, // TODO .is_comptime = false, // TODO
@ -267,8 +261,7 @@ pub fn interpret(
return InterpretResult{ .value = Value{ return InterpretResult{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = node_idx, .node_idx = node_idx,
.ty = Index.type_type, .index = struct_type,
.val = struct_type,
} }; } };
}, },
.error_set_decl => { .error_set_decl => {
@ -286,8 +279,7 @@ pub fn interpret(
const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{ const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{
.name = name, .name = name,
.node_idx = node_idx, .node_idx = node_idx,
.ty = .none, .index = .none,
.val = .none,
.alignment = 0, // TODO .alignment = 0, // TODO
.address_space = .generic, // TODO .address_space = .generic, // TODO
.is_pub = true, // TODO .is_pub = true, // TODO
@ -315,12 +307,16 @@ pub fn interpret(
if (type_value == null and init_value == null) return InterpretResult{ .nothing = {} }; if (type_value == null and init_value == null) return InterpretResult{ .nothing = {} };
if (type_value) |v| { if (type_value) |v| {
if (v.ty != Index.type_type) return InterpretResult{ .nothing = {} }; if (interpreter.ip.indexToKey(v.index).typeOf() != .type_type) {
return InterpretResult{ .nothing = {} };
}
} }
// TODO coerce `init_value` into `type_value`
const decl = interpreter.ip.getDecl(decl_index); const decl = interpreter.ip.getDecl(decl_index);
decl.ty = if (type_value) |v| v.val else init_value.?.ty; decl.index = if (type_value) |v| try interpreter.ip.get(interpreter.allocator, .{
decl.val = if (init_value) |init| init.val else .none; .unknown_value = .{ .ty = 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...) // TODO: Am I a dumbo shrimp? (e.g. is this tree shaking correct? works on my machine so like...)
@ -416,20 +412,10 @@ pub fn interpret(
}); });
if (simples.get(identifier)) |index| { if (simples.get(identifier)) |index| {
const ty: Index = switch (index) {
.undefined_value => .undefined_type,
.void_value => .void_type,
.unreachable_value => .noreturn_type,
.null_value => .null_type,
.bool_true => .bool_type,
.bool_false => .bool_type,
else => .type_type,
};
return InterpretResult{ .value = Value{ return InterpretResult{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = node_idx, .node_idx = node_idx,
.ty = ty, .index = index,
.val = index,
} }; } };
} }
@ -437,8 +423,7 @@ pub fn interpret(
return InterpretResult{ .value = Value{ return InterpretResult{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = node_idx, .node_idx = node_idx,
.ty = Index.type_type, .index = try interpreter.ip.get(interpreter.allocator, Key{ .int_type = .{
.val = try interpreter.ip.get(interpreter.allocator, Key{ .int_type = .{
.signedness = if (identifier[0] == 'u') .unsigned else .signed, .signedness = if (identifier[0] == 'u') .unsigned else .signed,
.bits = std.fmt.parseInt(u16, identifier[1..], 10) catch break :blk, .bits = std.fmt.parseInt(u16, identifier[1..], 10) catch break :blk,
} }), } }),
@ -448,12 +433,11 @@ pub fn interpret(
// Logic to find identifiers in accessible scopes // Logic to find identifiers in accessible scopes
if (interpreter.huntItDown(namespace, identifier, options)) |decl_index| { if (interpreter.huntItDown(namespace, identifier, options)) |decl_index| {
const decl = interpreter.ip.getDecl(decl_index); const decl = interpreter.ip.getDecl(decl_index);
if (decl.ty == .none) return InterpretResult{ .nothing = {} }; if (decl.index == .none) return InterpretResult{ .nothing = {} };
return InterpretResult{ .value = Value{ return InterpretResult{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = decl.node_idx, .node_idx = decl.node_idx,
.ty = decl.ty, .index = decl.index,
.val = decl.val,
} }; } };
} }
@ -471,31 +455,39 @@ pub fn interpret(
const field_name = tree.tokenSlice(data[node_idx].rhs); const field_name = tree.tokenSlice(data[node_idx].rhs);
var ir = try interpreter.interpret(data[node_idx].lhs, namespace, options); var ir = try interpreter.interpret(data[node_idx].lhs, namespace, options);
var irv = try ir.getValue(); var ir_value = try ir.getValue();
const lhs = interpreter.ip.indexToKey(irv.ty); const val_index = ir_value.index;
const inner_lhs = switch (lhs) { const val = interpreter.ip.indexToKey(val_index);
.pointer_type => |info| if (info.size == .One) interpreter.ip.indexToKey(info.elem_type) else lhs, std.debug.assert(val.typeOf() != .none);
else => lhs, const ty = interpreter.ip.indexToKey(val.typeOf());
const inner_ty = switch (ty) {
.pointer_type => |info| if (info.size == .One) interpreter.ip.indexToKey(info.elem_type) else ty,
else => ty,
}; };
const can_have_fields: bool = switch (inner_lhs) { const can_have_fields: bool = switch (inner_ty) {
.simple_type => |simple| switch (simple) { .simple_type => |simple| switch (simple) {
.type => blk: { .type => blk: {
if (irv.val == .none) break :blk true; if (interpreter.huntItDown(val.getNamespace(interpreter.ip), field_name, options)) |decl_index| {
const ty_key = interpreter.ip.indexToKey(irv.val);
if (interpreter.huntItDown(ty_key.getNamespace(interpreter.ip), field_name, options)) |decl_index| {
const decl = interpreter.ip.getDecl(decl_index); const decl = interpreter.ip.getDecl(decl_index);
return InterpretResult{ .value = Value{ return InterpretResult{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = node_idx, .node_idx = node_idx,
.ty = decl.ty, .index = decl.index,
.val = decl.val,
} }; } };
} }
switch (ty_key) { if (val == .unknown_value) {
return InterpretResult{ .value = Value{
.interpreter = interpreter,
.node_idx = data[node_idx].rhs,
.index = .unknown_unknown,
} };
}
switch (val) {
.error_set_type => |error_set_info| { // TODO .error_set_type => |error_set_info| { // TODO
_ = error_set_info; _ = error_set_info;
}, },
@ -508,8 +500,7 @@ pub fn interpret(
.value = Value{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = data[node_idx].rhs, .node_idx = data[node_idx].rhs,
.ty = irv.val, .index = .unknown_unknown, // TODO
.val = .none, // TODO resolve enum value
}, },
}; };
} }
@ -529,8 +520,10 @@ pub fn interpret(
.value = Value{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = data[node_idx].rhs, .node_idx = data[node_idx].rhs,
.ty = try interpreter.ip.get(interpreter.allocator, many_ptr_info), // TODO resolve ptr of Slice
.val = .none, // 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")) { } else if (std.mem.eql(u8, field_name, "len")) {
@ -538,8 +531,10 @@ pub fn interpret(
.value = Value{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = data[node_idx].rhs, .node_idx = data[node_idx].rhs,
.ty = Index.usize_type, // TODO resolve length of Slice
.val = .none, // TODO resolve length of Slice .index = try interpreter.ip.get(interpreter.allocator, .{
.unknown_value = .{ .ty = Index.usize_type },
}),
}, },
}; };
} }
@ -549,8 +544,10 @@ pub fn interpret(
.value = Value{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = data[node_idx].rhs, .node_idx = data[node_idx].rhs,
.ty = Index.usize_type, // TODO resolve length of Slice
.val = .none, // TODO resolve length of Slice .index = try interpreter.ip.get(interpreter.allocator, .{
.unknown_value = .{ .ty = Index.usize_type },
}),
}, },
}; };
} }
@ -558,53 +555,58 @@ pub fn interpret(
break :blk true; break :blk true;
}, },
.array_type => |array_info| blk: { .array_type => |array_info| blk: {
const len_value = try interpreter.ip.get(interpreter.allocator, .{ .int_u64_value = array_info.len });
if (std.mem.eql(u8, field_name, "len")) { if (std.mem.eql(u8, field_name, "len")) {
return InterpretResult{ .value = Value{ return InterpretResult{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = data[node_idx].rhs, .node_idx = data[node_idx].rhs,
.ty = Index.comptime_int_type, .index = try interpreter.ip.get(interpreter.allocator, .{ .int_u64_value = .{
.val = len_value, .ty = .comptime_int_type,
.int = array_info.len,
} }),
} }; } };
} }
break :blk true; break :blk true;
}, },
.optional_type => |optional_info| blk: { .optional_type => |optional_info| blk: {
if (!std.mem.eql(u8, field_name, "?")) break :blk false; if (!std.mem.eql(u8, field_name, "?")) break :blk false;
if (irv.val == Index.null_value) {
if (val_index == .type_type) {
try interpreter.recordError( try interpreter.recordError(
node_idx, node_idx,
"null_unwrap", "null_unwrap",
"tried to unwrap optional of type `{}` which was null", "tried to unwrap optional of type `{}` which was null",
.{irv.ty.fmtType(interpreter.ip)}, .{optional_info.payload_type.fmt(interpreter.ip)},
); );
return error.InvalidOperation; return error.InvalidOperation;
} else {
return InterpretResult{ .value = Value{
.interpreter = interpreter,
.node_idx = data[node_idx].rhs,
.ty = optional_info.payload_type,
.val = irv.val,
} };
} }
const result = switch (val) {
.optional_value => |optional_val| optional_val.val,
.unknown_value => val_index,
else => return error.InvalidOperation,
};
return InterpretResult{ .value = Value{
.interpreter = interpreter,
.node_idx = data[node_idx].rhs,
.index = result,
} };
}, },
.struct_type => |struct_index| blk: { .struct_type => |struct_index| blk: {
const struct_info = interpreter.ip.getStruct(struct_index); const struct_info = interpreter.ip.getStruct(struct_index);
if (struct_info.fields.getIndex(field_name)) |field_index| { if (struct_info.fields.getIndex(field_name)) |field_index| {
const field = struct_info.fields.values()[field_index]; const field = struct_info.fields.values()[field_index];
const val = found_val: {
if (irv.val == .none) break :found_val .none; const result = switch (val) {
const val_key = interpreter.ip.indexToKey(irv.val); .aggregate => |aggregate| aggregate.values[field_index],
if (val_key != .aggregate) break :found_val .none; .unknown_value => try interpreter.ip.get(interpreter.allocator, .{
break :found_val val_key.aggregate[field_index]; .unknown_value = .{ .ty = field.ty },
}),
else => return error.InvalidOperation,
}; };
return InterpretResult{ .value = Value{ return InterpretResult{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = data[node_idx].rhs, .node_idx = data[node_idx].rhs,
.ty = field.ty, .index = result,
.val = val,
} }; } };
} }
break :blk true; break :blk true;
@ -617,26 +619,51 @@ pub fn interpret(
_ = union_info; _ = union_info;
break :blk true; break :blk true;
}, },
else => false, .int_type,
.error_union_type,
.error_set_type,
.function_type,
.tuple_type,
.vector_type,
.anyframe_type,
=> false,
.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,
.bytes,
.optional_value,
.slice,
.aggregate,
.union_value,
.unknown_value,
=> unreachable,
}; };
const accessed_ty = if (inner_lhs == .simple_type and inner_lhs.simple_type == .type) irv.val else irv.ty; const accessed_ty = if (inner_ty == .simple_type and inner_ty.simple_type == .type) val else inner_ty;
if (accessed_ty != .none) { if (can_have_fields) {
if (can_have_fields) { try interpreter.recordError(
try interpreter.recordError( node_idx,
node_idx, "undeclared_identifier",
"undeclared_identifier", "`{}` has no member '{s}'",
"`{}` has no member '{s}'", .{ accessed_ty.fmt(interpreter.ip), field_name },
.{ accessed_ty.fmtType(interpreter.ip), field_name }, );
); } else {
} else { try interpreter.recordError(
try interpreter.recordError( node_idx,
node_idx, "invalid_field_access",
"invalid_field_access", "`{}` does not support field access",
"`{}` does not support field access", .{accessed_ty.fmt(interpreter.ip)},
.{accessed_ty.fmtType(interpreter.ip)}, );
);
}
} }
return error.InvalidOperation; return error.InvalidOperation;
}, },
@ -660,19 +687,39 @@ pub fn interpret(
.if_simple, .if_simple,
=> { => {
const if_info = ast.fullIf(tree, node_idx).?; const if_info = ast.fullIf(tree, node_idx).?;
// TODO: Don't evaluate runtime ifs
// if (options.observe_values) { // if (options.observe_values) {
const ir = try interpreter.interpret(if_info.ast.cond_expr, namespace, options); const ir = try interpreter.interpret(if_info.ast.cond_expr, namespace, options);
const condition = (try ir.getValue()).val; const condition = (try ir.getValue()).index;
std.debug.assert(condition == Index.bool_false or condition == Index.bool_true); const condition_val = interpreter.ip.indexToKey(condition);
if (condition == Index.bool_true) { const condition_ty = condition_val.typeOf();
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 (condition_val == .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); return try interpreter.interpret(if_info.ast.then_expr, namespace, options);
} else { } else {
if (if_info.ast.else_expr != 0) { if (if_info.ast.else_expr != 0) {
return try interpreter.interpret(if_info.ast.else_expr, namespace, options); return try interpreter.interpret(if_info.ast.else_expr, namespace, options);
} else return InterpretResult{ .nothing = {} }; }
} }
return InterpretResult{ .nothing = {} };
}, },
.equal_equal => { .equal_equal => {
var a = try interpreter.interpret(data[node_idx].lhs, namespace, options); var a = try interpreter.interpret(data[node_idx].lhs, namespace, options);
@ -683,8 +730,7 @@ pub fn interpret(
.value = Value{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = node_idx, .node_idx = node_idx,
.ty = Index.bool_type, .index = if (a_value.index == b_value.index) .bool_true else .bool_false, // TODO eql function required?
.val = if (a_value.val == b_value.val) Index.bool_true else Index.bool_false, // TODO eql function required?
}, },
}; };
}, },
@ -700,19 +746,26 @@ pub fn interpret(
interpreter.allocator, interpreter.allocator,
switch (nl) { switch (nl) {
.float => Key{ .float => Key{
.float_128_value = try std.fmt.parseFloat(f128, s), .float_comptime_value = try std.fmt.parseFloat(f128, s),
}, },
.int => if (s[0] == '-') Key{ .int => if (s[0] == '-') Key{
.int_i64_value = try std.fmt.parseInt(i64, s, 0), .int_i64_value = .{
.ty = number_type,
.int = try std.fmt.parseInt(i64, s, 0),
},
} else Key{ } else Key{
.int_u64_value = try std.fmt.parseInt(u64, s, 0), .int_u64_value = .{
.ty = number_type,
.int = try std.fmt.parseInt(u64, s, 0),
},
}, },
.big_int => |base| blk: { .big_int => |base| blk: {
var big_int = try std.math.big.int.Managed.init(interpreter.allocator); var big_int = try std.math.big.int.Managed.init(interpreter.allocator);
defer big_int.deinit(); defer big_int.deinit();
const prefix_length: usize = if (base != .decimal) 2 else 0; const prefix_length: usize = if (base != .decimal) 2 else 0;
try big_int.setString(@enumToInt(base), s[prefix_length..]); try big_int.setString(@enumToInt(base), s[prefix_length..]);
break :blk Key{ .int_big_value = big_int.toConst() }; std.debug.assert(number_type == .comptime_int_type);
break :blk Key{ .int_big_value = .{ .ty = number_type, .int = big_int.toConst() } };
}, },
.failure => return error.CriticalAstFailure, .failure => return error.CriticalAstFailure,
}, },
@ -721,8 +774,7 @@ pub fn interpret(
return InterpretResult{ .value = Value{ return InterpretResult{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = node_idx, .node_idx = node_idx,
.ty = number_type, .index = value,
.val = value,
} }; } };
}, },
.assign, .assign,
@ -747,12 +799,17 @@ pub fn interpret(
return InterpretResult{ .nothing = {} }; return InterpretResult{ .nothing = {} };
} }
var ir = try interpreter.interpret(data[node_idx].lhs, namespace, options); const lhs = try interpreter.interpret(data[node_idx].lhs, namespace, options);
var to_value = try ir.getValue(); const rhs = try interpreter.interpret(data[node_idx].rhs, namespace, options);
var from_value = (try (try interpreter.interpret(data[node_idx].rhs, namespace, options)).getValue());
const to_val = try lhs.getValue();
const from_val = try rhs.getValue();
const to_ty = interpreter.ip.indexToKey(to_val.index).typeOf();
const from_ty = interpreter.ip.indexToKey(from_val.index).typeOf();
// TODO report error // TODO report error
_ = try interpreter.ip.cast(interpreter.allocator, to_value.ty, from_value.ty, builtin.target); _ = try interpreter.ip.cast(interpreter.allocator, to_ty, from_ty, builtin.target);
return InterpretResult{ .nothing = {} }; return InterpretResult{ .nothing = {} };
}, },
@ -786,11 +843,14 @@ pub fn interpret(
try writer.writeAll("log: "); try writer.writeAll("log: ");
for (params, 0..) |param, index| { for (params, 0..) |param, index| {
var value = (try interpreter.interpret(param, namespace, options)).maybeGetValue() orelse { const ir_value = (try interpreter.interpret(param, namespace, options)).maybeGetValue() orelse {
try writer.writeAll("indeterminate"); try writer.writeAll("indeterminate");
continue; continue;
}; };
try writer.print("@as({}, {})", .{ value.ty.fmtType(interpreter.ip), value.val.fmtValue(value.ty, interpreter.ip) }); const val = interpreter.ip.indexToKey(ir_value.index);
const ty = val.typeOf();
try writer.print("@as({}, {})", .{ ty.fmt(interpreter.ip), val.fmt(interpreter.ip) });
if (index != params.len - 1) if (index != params.len - 1)
try writer.writeAll(", "); try writer.writeAll(", ");
} }
@ -827,8 +887,9 @@ pub fn interpret(
return InterpretResult{ .value = Value{ return InterpretResult{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = node_idx, .node_idx = node_idx,
.ty = try interpreter.ip.get(interpreter.allocator, Key{ .struct_type = struct_index }), .index = try interpreter.ip.get(interpreter.allocator, .{ .unknown_value = .{
.val = Index.undefined_value, .ty = try interpreter.ip.get(interpreter.allocator, .{ .struct_type = struct_index }),
} }),
} }; } };
} }
@ -842,38 +903,59 @@ pub fn interpret(
.value = Value{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = node_idx, .node_idx = node_idx,
.ty = Index.type_type, .index = try interpreter.ip.get(interpreter.allocator, .{ .unknown_value = .{ .ty = .type_type } }),
.val = .none, // TODO
}, },
}; };
} }
if (std.mem.eql(u8, call_name, "@TypeOf")) { if (std.mem.eql(u8, call_name, "@TypeOf")) {
if (params.len != 1) return error.InvalidBuiltin; 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.indexToKey(value.index).typeOf();
}
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;
}
const value = try (try interpreter.interpret(params[0], namespace, options)).getValue();
return InterpretResult{ .value = Value{ return InterpretResult{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = node_idx, .node_idx = node_idx,
.ty = Index.type_type, .index = peer_type,
.val = value.ty,
} }; } };
} }
if (std.mem.eql(u8, call_name, "@hasDecl")) { if (std.mem.eql(u8, call_name, "@hasDecl")) {
if (params.len != 2) return error.InvalidBuiltin; if (params.len != 2) return error.InvalidBuiltin;
const value = try (try interpreter.interpret(params[0], namespace, options)).getValue(); const ir_value = try (try interpreter.interpret(params[0], namespace, options)).getValue();
const field_name = try (try interpreter.interpret(params[1], namespace, options)).getValue(); const field_name = try (try interpreter.interpret(params[1], namespace, options)).getValue();
if (value.ty != Index.type_type or value.ty == .none) return error.InvalidBuiltin; const val = interpreter.ip.indexToKey(ir_value.index);
if (interpreter.ip.indexToKey(field_name.ty) != .pointer_type) return error.InvalidBuiltin; // Check if it's a []const u8 const ty = val.typeOf();
if (value.val == .none) return error.InvalidBuiltin;
const value_namespace = interpreter.ip.indexToKey(value.val).getNamespace(interpreter.ip); if (ty != .type_type) return error.InvalidBuiltin;
const value_namespace = interpreter.ip.indexToKey(ty).getNamespace(interpreter.ip);
if (value_namespace == .none) return error.InvalidBuiltin; if (value_namespace == .none) return error.InvalidBuiltin;
const name = interpreter.ip.indexToKey(field_name.val).bytes; // TODO add checks const name = interpreter.ip.indexToKey(field_name.index).bytes; // TODO add checks
const decls = interpreter.namespaces.items(.decls)[@enumToInt(value_namespace)]; const decls = interpreter.namespaces.items(.decls)[@enumToInt(value_namespace)];
const has_decl = decls.contains(name); const has_decl = decls.contains(name);
@ -881,8 +963,7 @@ pub fn interpret(
return InterpretResult{ .value = Value{ return InterpretResult{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = node_idx, .node_idx = node_idx,
.ty = Index.bool_type, .index = if (has_decl) .bool_true else .bool_false,
.val = if (has_decl) Index.bool_true else Index.bool_false,
} }; } };
} }
@ -890,16 +971,20 @@ pub fn interpret(
if (params.len != 2) return error.InvalidBuiltin; if (params.len != 2) return error.InvalidBuiltin;
const as_type = try (try interpreter.interpret(params[0], namespace, options)).getValue(); const as_type = try (try interpreter.interpret(params[0], namespace, options)).getValue();
const value = try (try interpreter.interpret(params[1], namespace, options)).getValue(); // const value = try (try interpreter.interpret(params[1], namespace, options)).getValue();
if (as_type.ty != Index.type_type) return error.InvalidBuiltin; if (interpreter.ip.indexToKey(as_type.index).typeOf() != .type_type) {
return error.InvalidBuiltin;
}
return InterpretResult{ return InterpretResult{
.value = Value{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = node_idx, .node_idx = node_idx,
.ty = as_type.val, // TODO port Sema.coerceExtra to InternPool
.val = value.val, // TODO port Sema.coerceExtra to InternPool .index = try interpreter.ip.get(interpreter.allocator, .{
.unknown_value = .{ .ty = as_type.index },
}),
}, },
}; };
} }
@ -910,26 +995,25 @@ pub fn interpret(
.string_literal => { .string_literal => {
const str = tree.getNodeSource(node_idx)[1 .. tree.getNodeSource(node_idx).len - 1]; 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 = .{ // const string_literal_type = try interpreter.ip.get(interpreter.allocator, Key{ .pointer_type = .{
.elem_type = try interpreter.ip.get(interpreter.allocator, Key{ .array_type = .{ // .elem_type = try interpreter.ip.get(interpreter.allocator, Key{ .array_type = .{
.child = Index.u8_type, // .child = Index.u8_type,
.len = @intCast(u64, str.len), // .len = @intCast(u64, str.len),
.sentinel = try interpreter.ip.get(interpreter.allocator, Key{ .int_u64_value = 0 }), // .sentinel = try interpreter.ip.get(interpreter.allocator, Key{ .int_u64_value = .{ .ty = .u8_type, .int = 0 } }),
} }), // } }),
.sentinel = .none, // .sentinel = .none,
.alignment = 0, // .alignment = 0,
.size = .One, // .size = .One,
.is_const = true, // .is_const = true,
.is_volatile = false, // .is_volatile = false,
.is_allowzero = false, // .is_allowzero = false,
.address_space = .generic, // .address_space = .generic,
} }); // } });
return InterpretResult{ .value = Value{ return InterpretResult{ .value = Value{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = node_idx, .node_idx = node_idx,
.ty = string_literal_type, .index = try interpreter.ip.get(interpreter.allocator, Key{ .bytes = str }),
.val = try interpreter.ip.get(interpreter.allocator, Key{ .bytes = str }),
} }; } };
}, },
// TODO: Add comptime autodetection; e.g. const MyArrayList = std.ArrayList(u8) // TODO: Add comptime autodetection; e.g. const MyArrayList = std.ArrayList(u8)
@ -989,8 +1073,7 @@ pub fn interpret(
const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{ const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{
.name = name, .name = name,
.node_idx = node_idx, .node_idx = node_idx,
.ty = Index.type_type, .index = function_type,
.val = function_type,
.alignment = 0, // TODO .alignment = 0, // TODO
.address_space = .generic, // TODO .address_space = .generic, // TODO
.is_pub = false, // TODO .is_pub = false, // TODO
@ -1034,24 +1117,36 @@ pub fn interpret(
}, },
.bool_not => { .bool_not => {
const result = try interpreter.interpret(data[node_idx].lhs, namespace, .{}); const result = try interpreter.interpret(data[node_idx].lhs, namespace, .{});
const value = try result.getValue(); const ir_value = try result.getValue();
if (value.ty != Index.bool_type) { const val = ir_value.index;
const ty = interpreter.ip.indexToKey(ir_value.index).typeOf();
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( try interpreter.recordError(
node_idx, node_idx,
"invalid_deref", "invalid_deref",
"expected type `bool` but got `{}`", "expected type `bool` but got `{}`",
.{value.ty.fmtType(interpreter.ip)}, .{ty.fmt(interpreter.ip)},
); );
return error.InvalidOperation; return error.InvalidOperation;
} }
std.debug.assert(value.val == Index.bool_false or value.val == Index.bool_true); std.debug.assert(val == .bool_false or val == .bool_true);
return InterpretResult{ .value = .{ return InterpretResult{ .value = .{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = node_idx, .node_idx = node_idx,
.ty = Index.bool_type, .index = if (val == .bool_false) .bool_true else .bool_false,
.val = if (value.val == Index.bool_false) Index.bool_true else Index.bool_false,
} }; } };
}, },
.address_of => { .address_of => {
@ -1059,10 +1154,12 @@ pub fn interpret(
// variables are the only non-const(?) // variables are the only non-const(?)
const result = try interpreter.interpret(data[node_idx].lhs, namespace, .{}); const result = try interpreter.interpret(data[node_idx].lhs, namespace, .{});
const value = (try result.getValue()); const ir_value = try result.getValue();
const ty = interpreter.ip.indexToKey(ir_value.index).typeOf();
const pointer_type = try interpreter.ip.get(interpreter.allocator, Key{ .pointer_type = .{ const pointer_type = try interpreter.ip.get(interpreter.allocator, Key{ .pointer_type = .{
.elem_type = value.ty, .elem_type = ty,
.sentinel = .none, .sentinel = .none,
.alignment = 0, .alignment = 0,
.size = .One, .size = .One,
@ -1075,15 +1172,26 @@ pub fn interpret(
return InterpretResult{ .value = .{ return InterpretResult{ .value = .{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = node_idx, .node_idx = node_idx,
.ty = pointer_type, .index = try interpreter.ip.get(interpreter.allocator, .{
.val = value.val, .unknown_value = .{ .ty = pointer_type },
}),
} }; } };
}, },
.deref => { .deref => {
const result = try interpreter.interpret(data[node_idx].lhs, namespace, .{}); const result = try interpreter.interpret(data[node_idx].lhs, namespace, .{});
const value = (try result.getValue()); const ir_value = (try result.getValue());
const type_key = interpreter.ip.indexToKey(value.ty); const ty = interpreter.ip.indexToKey(ir_value.index).typeOf();
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) { if (type_key != .pointer_type) {
try interpreter.recordError(node_idx, "invalid_deref", "cannot deference non-pointer", .{}); try interpreter.recordError(node_idx, "invalid_deref", "cannot deference non-pointer", .{});
@ -1093,8 +1201,9 @@ pub fn interpret(
return InterpretResult{ .value = .{ return InterpretResult{ .value = .{
.interpreter = interpreter, .interpreter = interpreter,
.node_idx = node_idx, .node_idx = node_idx,
.ty = type_key.pointer_type.elem_type, .index = try interpreter.ip.get(interpreter.allocator, .{
.val = value.val, .unknown_value = .{ .ty = type_key.pointer_type.elem_type },
}),
} }; } };
}, },
else => { else => {
@ -1144,15 +1253,17 @@ pub fn call(
while (ast.nextFnParam(&arg_it)) |param| { while (ast.nextFnParam(&arg_it)) |param| {
if (arg_index >= arguments.len) return error.MissingArguments; if (arg_index >= arguments.len) return error.MissingArguments;
var tex = try (try interpreter.interpret(param.type_expr, fn_namespace, options)).getValue(); var tex = try (try interpreter.interpret(param.type_expr, fn_namespace, options)).getValue();
if (tex.ty != Index.type_type) { const tex_ty = interpreter.ip.indexToKey(tex.index).typeOf();
if (tex_ty != .type_type) {
try interpreter.recordError( try interpreter.recordError(
param.type_expr, param.type_expr,
"expected_type", "expected_type",
"expected type 'type', found '{}'", "expected type 'type', found '{}'",
.{tex.ty.fmtType(interpreter.ip)}, .{tex_ty.fmt(interpreter.ip)},
); );
return error.InvalidCast; return error.InvalidCast;
} }
// TODO validate that `arguments[arg_index].index`'s types matches tex.index
if (param.name_token) |name_token| { if (param.name_token) |name_token| {
const name = offsets.tokenToSlice(tree, name_token); const name = offsets.tokenToSlice(tree, name_token);
@ -1160,8 +1271,7 @@ pub fn call(
const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{ const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{
.name = name, .name = name,
.node_idx = name_token, .node_idx = name_token,
.ty = tex.val, .index = arguments[arg_index].index,
.val = arguments[arg_index].val,
.alignment = 0, // TODO .alignment = 0, // TODO
.address_space = .generic, // TODO .address_space = .generic, // TODO
.is_pub = true, // TODO .is_pub = true, // TODO

View File

@ -598,13 +598,14 @@ fn typeToCompletion(
null, null,
), ),
.primitive, .array_index => {}, .primitive, .array_index => {},
.@"comptime" => |co| { .@"comptime" => |co| try analyser.completions.dotCompletions(
if (type_handle.type.is_type_val) { allocator,
try analyser.completions.dotCompletions(allocator, list, &co.interpreter.ip, co.value.ty, co.value.val, co.value.node_idx); list,
} else { &co.interpreter.ip,
try analyser.completions.dotCompletions(allocator, list, &co.interpreter.ip, co.value.val, .none, co.value.node_idx); co.value.index,
} type_handle.type.is_type_val,
}, co.value.node_idx,
),
} }
} }
@ -921,7 +922,7 @@ fn hoverSymbol(server: *Server, decl_handle: analysis.DeclWithHandle) error{OutO
const resolved_type_str = if (resolved_type) |rt| const resolved_type_str = if (resolved_type) |rt|
if (rt.type.is_type_val) switch (rt.type.data) { if (rt.type.is_type_val) switch (rt.type.data) {
.@"comptime" => |co| try std.fmt.allocPrint(server.arena.allocator(), "{}", .{co.value.ty.fmtType(co.interpreter.ip)}), .@"comptime" => |co| try std.fmt.allocPrint(server.arena.allocator(), "{}", .{co.value.index.fmt(co.interpreter.ip)}),
else => "type", else => "type",
} else switch (rt.type.data) { // TODO: Investigate random weird numbers like 897 that cause index of bounds } else switch (rt.type.data) { // TODO: Investigate random weird numbers like 897 that cause index of bounds
.pointer, .pointer,

File diff suppressed because it is too large Load Diff

View File

@ -4,31 +4,36 @@ const types = @import("../lsp.zig");
const Ast = std.zig.Ast; const Ast = std.zig.Ast;
/// generates a list of dot completions for the given typed-value in `index`
/// the given `index` must belong to the given InternPool
pub fn dotCompletions( pub fn dotCompletions(
arena: std.mem.Allocator, arena: std.mem.Allocator,
completions: *std.ArrayListUnmanaged(types.CompletionItem), completions: *std.ArrayListUnmanaged(types.CompletionItem),
ip: *InternPool, ip: *InternPool,
ty: InternPool.Index, index: InternPool.Index,
val: InternPool.Index, is_type_val: bool,
node: ?Ast.Node.Index, node: ?Ast.Node.Index,
) error{OutOfMemory}!void { ) error{OutOfMemory}!void {
std.debug.assert(index != .none);
_ = node; _ = node;
const key = ip.indexToKey(ty); const index_key = ip.indexToKey(index);
const inner_key = switch (key) { const val: InternPool.Key = if (is_type_val) index_key else .{ .unknown_value = .{ .ty = index } };
.pointer_type => |info| if (info.size == .One) ip.indexToKey(info.elem_type) else key, const ty: InternPool.Key = if (is_type_val) ip.indexToKey(index_key.typeOf()) else index_key;
else => key,
const inner_ty = switch (ty) {
.pointer_type => |info| if (info.size == .One) ip.indexToKey(info.elem_type) else ty,
else => ty,
}; };
switch (inner_key) { switch (inner_ty) {
.simple_type => |simple| switch (simple) { .simple_type => |simple| switch (simple) {
.type => { .type => {
const ty_key = ip.indexToKey(val); const namespace = val.getNamespace(ip.*);
const namespace = ty_key.getNamespace(ip.*);
if (namespace != .none) { if (namespace != .none) {
// TODO lookup in namespace // TODO lookup in namespace
} }
switch (ty_key) { switch (val) {
.error_set_type => |error_set_info| { .error_set_type => |error_set_info| {
for (error_set_info.names) |name| { for (error_set_info.names) |name| {
const error_name = ip.indexToKey(name).bytes; const error_name = ip.indexToKey(name).bytes;
@ -64,7 +69,7 @@ pub fn dotCompletions(
try completions.append(arena, .{ try completions.append(arena, .{
.label = "ptr", .label = "ptr",
.kind = .Field, .kind = .Field,
.detail = try std.fmt.allocPrint(arena, "ptr: {}", .{many_ptr_info.fmtType(ip.*)}), .detail = try std.fmt.allocPrint(arena, "ptr: {}", .{many_ptr_info.fmt(ip.*)}),
}); });
try completions.append(arena, .{ try completions.append(arena, .{
.label = "len", .label = "len",
@ -80,7 +85,7 @@ pub fn dotCompletions(
} }
}, },
.array_type => |array_info| { .array_type => |array_info| {
try completions.append(arena, types.CompletionItem{ try completions.append(arena, .{
.label = "len", .label = "len",
.kind = .Field, .kind = .Field,
.detail = try std.fmt.allocPrint(arena, "const len: usize ({d})", .{array_info.len}), // TODO how should this be displayed .detail = try std.fmt.allocPrint(arena, "const len: usize ({d})", .{array_info.len}), // TODO how should this be displayed
@ -93,7 +98,7 @@ pub fn dotCompletions(
while (field_it.next()) |entry| { while (field_it.next()) |entry| {
const label = entry.key_ptr.*; const label = entry.key_ptr.*;
const field = entry.value_ptr.*; const field = entry.value_ptr.*;
completions.appendAssumeCapacity(types.CompletionItem{ completions.appendAssumeCapacity(.{
.label = label, .label = label,
.kind = .Field, .kind = .Field,
.detail = try std.fmt.allocPrint(arena, "{s}: {}", .{ .detail = try std.fmt.allocPrint(arena, "{s}: {}", .{
@ -107,7 +112,7 @@ pub fn dotCompletions(
try completions.append(arena, .{ try completions.append(arena, .{
.label = "?", .label = "?",
.kind = .Operator, .kind = .Operator,
.detail = try std.fmt.allocPrint(arena, "{}", .{optional_info.payload_type.fmtType(ip.*)}), .detail = try std.fmt.allocPrint(arena, "{}", .{optional_info.payload_type.fmt(ip.*)}),
}); });
}, },
.enum_type => |enum_index| { .enum_type => |enum_index| {
@ -116,7 +121,7 @@ pub fn dotCompletions(
try completions.append(arena, .{ try completions.append(arena, .{
.label = field_name, .label = field_name,
.kind = .Field, .kind = .Field,
.detail = try std.fmt.allocPrint(arena, "{}", .{field_value.fmtValue(enum_info.tag_type, ip.*)}), .detail = try std.fmt.allocPrint(arena, "{}", .{field_value.fmt(ip.*)}),
}); });
} }
}, },
@ -130,9 +135,9 @@ pub fn dotCompletions(
.label = label, .label = label,
.kind = .Field, .kind = .Field,
.detail = if (field.alignment != 0) .detail = if (field.alignment != 0)
try std.fmt.allocPrint(arena, "{s}: align({d}) {}", .{ label, field.alignment, field.ty.fmtType(ip.*) }) try std.fmt.allocPrint(arena, "{s}: align({d}) {}", .{ label, field.alignment, field.ty.fmt(ip.*) })
else else
try std.fmt.allocPrint(arena, "{s}: {}", .{ label, field.ty.fmtType(ip.*) }), try std.fmt.allocPrint(arena, "{s}: {}", .{ label, field.ty.fmt(ip.*) }),
}); });
} }
}, },
@ -141,7 +146,7 @@ pub fn dotCompletions(
try completions.append(arena, .{ try completions.append(arena, .{
.label = try std.fmt.allocPrint(arena, "{d}", .{i}), .label = try std.fmt.allocPrint(arena, "{d}", .{i}),
.kind = .Field, .kind = .Field,
.detail = try std.fmt.allocPrint(arena, "{d}: {}", .{ i, tuple_ty.fmtType(ip.*) }), .detail = try std.fmt.allocPrint(arena, "{d}: {}", .{ i, tuple_ty.fmt(ip.*) }),
}); });
} }
}, },
@ -162,11 +167,15 @@ pub fn dotCompletions(
.float_64_value, .float_64_value,
.float_80_value, .float_80_value,
.float_128_value, .float_128_value,
.float_comptime_value,
=> unreachable, => unreachable,
.bytes, .bytes,
.optional_value,
.slice,
.aggregate, .aggregate,
.union_value, .union_value,
.unknown_value,
=> unreachable, => unreachable,
} }
} }
@ -194,9 +203,9 @@ fn formatFieldDetail(
if (field.alignment != 0) { if (field.alignment != 0) {
try writer.print("align({d}) ", .{field.alignment}); try writer.print("align({d}) ", .{field.alignment});
} }
try writer.print("{}", .{field.ty.fmtType(ctx.ip.*)}); try writer.print("{}", .{field.ty.fmt(ctx.ip.*)});
if (field.default_value != .none) { if (field.default_value != .none) {
try writer.print(" = {},", .{field.default_value.fmtValue(field.ty, ctx.ip.*)}); try writer.print(" = {},", .{field.default_value.fmt(ctx.ip.*)});
} }
} }

View File

@ -788,6 +788,7 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, node_handle: NodeWithHan
} }
return null; return null;
}; };
const is_type_val = interpreter.ip.indexToKey(value.index).typeOf() == .type_type;
return TypeWithHandle{ return TypeWithHandle{
.type = .{ .type = .{
@ -795,7 +796,7 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, node_handle: NodeWithHan
.interpreter = interpreter, .interpreter = interpreter,
.value = value, .value = value,
} }, } },
.is_type_val = value.ty == InternPool.Index.type_type, .is_type_val = is_type_val,
}, },
.handle = node_handle.handle, .handle = node_handle.handle,
}; };

View File

@ -14,39 +14,50 @@ const offsets = zls.offsets;
const allocator: std.mem.Allocator = std.testing.allocator; const allocator: std.mem.Allocator = std.testing.allocator;
test "ComptimeInterpreter - primitive types" { test "ComptimeInterpreter - primitive types" {
try testExpr("true", .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); try testExpr("true", .{ .simple_value = .bool_true });
try testExpr("false", .{ .simple_type = .bool }, .{ .simple_value = .bool_false }); try testExpr("false", .{ .simple_value = .bool_false });
try testExpr("5", .{ .simple_type = .comptime_int }, .{ .int_u64_value = 5 }); try testExpr("5", .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 5 } });
// TODO try testExpr("-2", .{ .simple_type = .comptime_int }, .{ .int_i64_value = -2 }); // TODO try testExpr("-2", .{ .int_i64_value = .{ .ty = .comptime_int, .int = -2 } });
try testExpr("3.0", .{ .simple_type = .comptime_float }, null); try testExpr("3.0", .{ .float_comptime_value = 3.0 });
try testExpr("null", .{ .simple_type = .null_type }, .{ .simple_value = .null_value }); try testExpr("null", .{ .simple_value = .null_value });
try testExpr("void", .{ .simple_type = .type }, .{ .simple_type = .void }); try testExpr("void", .{ .simple_type = .void });
try testExpr("undefined", .{ .simple_type = .undefined_type }, .{ .simple_value = .undefined_value }); try testExpr("undefined", .{ .simple_value = .undefined_value });
try testExpr("noreturn", .{ .simple_type = .type }, .{ .simple_type = .noreturn }); try testExpr("noreturn", .{ .simple_type = .noreturn });
} }
test "ComptimeInterpreter - expressions" { test "ComptimeInterpreter - expressions" {
if (true) return error.SkipZigTest; // TODO if (true) return error.SkipZigTest; // TODO
try testExpr("5 + 3", .{ .simple_type = .comptime_int }, .{ .int_u64_value = 8 }); 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("5.2 + 4.2", .{ .simple_type = .comptime_float }, null);
try testExpr("3 == 3", .{ .simple_type = .bool }, .{ .simple_valueclear = .bool_true }); try testExpr("3 == 3", .{ .simple_valueclear = .bool_true });
try testExpr("5.2 == 2.1", .{ .simple_type = .bool }, .{ .simple_value = .bool_false }); try testExpr("5.2 == 2.1", .{ .simple_value = .bool_false });
try testExpr("@as(?bool, null) orelse true", .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); try testExpr("@as(?bool, null) orelse true", .{ .simple_value = .bool_true });
} }
test "ComptimeInterpreter - builtins" { test "ComptimeInterpreter - builtins" {
if (true) return error.SkipZigTest; // TODO if (true) return error.SkipZigTest; // TODO
try testExpr("@as(bool, true)", .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); try testExpr("@as(bool, true)", .{ .simple_value = .bool_true });
try testExpr("@as(u32, 3)", .{ .int_type = .{ try testExpr("@as(u32, 3)", .{ .int_u64_value = .{ .ty = .u32_type, .int = 3 } });
.signedness = .unsigned, }
.bits = 32,
} }, .{ .int_u64_value = 3 }); test "ComptimeInterpreter - @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 "ComptimeInterpreter - string literal" { test "ComptimeInterpreter - string literal" {
if (true) return error.SkipZigTest; // TODO
var context = try Context.init( var context = try Context.init(
\\const foobarbaz = "hello world!"; \\const foobarbaz = "hello world!";
\\ \\
@ -64,12 +75,12 @@ test "ComptimeInterpreter - labeled block" {
\\blk: { \\blk: {
\\ break :blk true; \\ break :blk true;
\\} \\}
, .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); , .{ .simple_value = .bool_true });
try testExpr( try testExpr(
\\blk: { \\blk: {
\\ break :blk 3; \\ break :blk 3;
\\} \\}
, .{ .simple_type = .comptime_int }, .{ .int_u64_value = 3 }); , .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 3 } });
} }
test "ComptimeInterpreter - if" { test "ComptimeInterpreter - if" {
@ -77,18 +88,18 @@ test "ComptimeInterpreter - if" {
\\blk: { \\blk: {
\\ break :blk if (true) true else false; \\ break :blk if (true) true else false;
\\} \\}
, .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); , .{ .simple_value = .bool_true });
try testExpr( try testExpr(
\\blk: { \\blk: {
\\ break :blk if (false) true else false; \\ break :blk if (false) true else false;
\\} \\}
, .{ .simple_type = .bool }, .{ .simple_value = .bool_false }); , .{ .simple_value = .bool_false });
try testExpr( try testExpr(
\\blk: { \\blk: {
\\ if (false) break :blk true; \\ if (false) break :blk true;
\\ break :blk false; \\ break :blk false;
\\} \\}
, .{ .simple_type = .bool }, .{ .simple_value = .bool_false }); , .{ .simple_value = .bool_false });
// TODO // TODO
// try testExpr( // try testExpr(
// \\outer: { // \\outer: {
@ -97,7 +108,7 @@ test "ComptimeInterpreter - if" {
// \\ }) break :outer true; // \\ }) break :outer true;
// \\ break :outer false; // \\ break :outer false;
// \\} // \\}
// , .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); // , .{ .simple_value = .bool_true });
} }
test "ComptimeInterpreter - variable lookup" { test "ComptimeInterpreter - variable lookup" {
@ -106,7 +117,7 @@ test "ComptimeInterpreter - variable lookup" {
\\ var foo = 42; \\ var foo = 42;
\\ break :blk foo; \\ break :blk foo;
\\} \\}
, .{ .simple_type = .comptime_int }, .{ .int_u64_value = 42 }); , .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 42 } });
try testExpr( try testExpr(
\\blk: { \\blk: {
\\ var foo = 1; \\ var foo = 1;
@ -114,7 +125,7 @@ test "ComptimeInterpreter - variable lookup" {
\\ var baz = 3; \\ var baz = 3;
\\ break :blk bar; \\ break :blk bar;
\\} \\}
, .{ .simple_type = .comptime_int }, .{ .int_u64_value = 2 }); , .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 2 } });
var context = try Context.init( var context = try Context.init(
\\const bar = foo; \\const bar = foo;
@ -123,26 +134,25 @@ test "ComptimeInterpreter - variable lookup" {
defer context.deinit(); defer context.deinit();
const result = try context.interpret(context.findVar("bar")); const result = try context.interpret(context.findVar("bar"));
try expectEqualKey(context.interpreter.ip, .{ .simple_type = .comptime_int }, result.ty); try expectEqualKey(context.interpreter.ip, .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 3 } }, result.val);
try expectEqualKey(context.interpreter.ip, .{ .int_u64_value = 3 }, result.val);
} }
test "ComptimeInterpreter - field access" { test "ComptimeInterpreter - field access" {
try testExpr( try testExpr(
\\blk: { \\blk: {
\\ const foo: struct {alpha: u64, beta: bool} = undefined; \\ const foo: struct {alpha: u64, beta: bool} = undefined;
\\ break :blk foo.beta; \\ break :blk @TypeOf(foo.beta);
\\} \\}
, .{ .simple_type = .bool }, null); , .{ .simple_type = .bool });
try testExpr( try testExpr(
\\blk: { \\blk: {
\\ const foo: struct {alpha: u64, beta: bool} = undefined; \\ const foo: struct {alpha: u64, beta: bool} = undefined;
\\ break :blk foo.alpha; \\ break :blk @TypeOf(foo.alpha);
\\} \\}
, .{ .int_type = .{ , .{ .int_type = .{
.signedness = .unsigned, .signedness = .unsigned,
.bits = 64, .bits = 64,
} }, null); } });
} }
test "ComptimeInterpreter - optional operations" { test "ComptimeInterpreter - optional operations" {
@ -152,13 +162,13 @@ test "ComptimeInterpreter - optional operations" {
\\ const foo: ?bool = true; \\ const foo: ?bool = true;
\\ break :blk foo.?; \\ break :blk foo.?;
\\} \\}
, .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); , .{ .simple_value = .bool_true });
try testExpr( try testExpr(
\\blk: { \\blk: {
\\ const foo: ?bool = true; \\ const foo: ?bool = true;
\\ break :blk foo == null; \\ break :blk foo == null;
\\} \\}
, .{ .simple_type = .bool }, .{ .simple_value = .bool_false }); , .{ .simple_value = .bool_false });
} }
test "ComptimeInterpreter - pointer operations" { test "ComptimeInterpreter - pointer operations" {
@ -168,20 +178,20 @@ test "ComptimeInterpreter - pointer operations" {
\\ const foo: []const u8 = ""; \\ const foo: []const u8 = "";
\\ break :blk foo.len; \\ break :blk foo.len;
\\} \\}
, .{ .simple_type = .usize }, .{ .bytes = "" }); , .{ .int_u64_value = .{ .ty = .usize_type, .int = 0 } });
try testExpr( try testExpr(
\\blk: { \\blk: {
\\ const foo = true; \\ const foo = true;
\\ break :blk &foo; \\ break :blk &foo;
\\} \\}
, @panic("TODO"), .{ .simple_value = .bool_true }); , .{ .simple_value = .bool_true });
try testExpr( try testExpr(
\\blk: { \\blk: {
\\ const foo = true; \\ const foo = true;
\\ const bar = &foo; \\ const bar = &foo;
\\ break :blk bar.*; \\ break :blk bar.*;
\\} \\}
, @panic("TODO"), .{ .simple_value = .bool_true }); , .{ .simple_value = .bool_true });
} }
test "ComptimeInterpreter - call return primitive type" { test "ComptimeInterpreter - call return primitive type" {
@ -357,20 +367,24 @@ const Context = struct {
args[i] = .{ args[i] = .{
.interpreter = self.interpreter, .interpreter = self.interpreter,
.node_idx = 0, .node_idx = 0,
.ty = try self.interpreter.ip.get(self.interpreter.allocator, argument.ty), .index = if (argument.val) |val|
.val = if (argument.val) |val| try self.interpreter.ip.get(self.interpreter.allocator, val) else .none, try self.interpreter.ip.get(self.interpreter.allocator, val)
else
try self.interpreter.ip.get(self.interpreter.allocator, .{
.unknown_value = .{ .ty = try self.interpreter.ip.get(self.interpreter.allocator, argument.ty) },
}),
}; };
} }
const namespace = @intToEnum(ComptimeInterpreter.Namespace.Index, 0); // root namespace const namespace = @intToEnum(ComptimeInterpreter.Namespace.Index, 0); // root namespace
const result = (try self.interpreter.call(namespace, func_node, args, .{})).result; const result = (try self.interpreter.call(namespace, func_node, args, .{})).result;
try std.testing.expect(result == .value); const val = self.interpreter.ip.indexToKey(result.value.index);
try std.testing.expect(result.value.ty != .none); const ty = self.interpreter.ip.indexToKey(val.typeOf());
return KV{ return KV{
.ty = self.interpreter.ip.indexToKey(result.value.ty), .ty = ty,
.val = if (result.value.val == .none) null else self.interpreter.ip.indexToKey(result.value.val), .val = val,
}; };
} }
@ -378,11 +392,12 @@ const Context = struct {
const namespace = @intToEnum(ComptimeInterpreter.Namespace.Index, 0); // root namespace const namespace = @intToEnum(ComptimeInterpreter.Namespace.Index, 0); // root namespace
const result = try (try self.interpreter.interpret(node, namespace, .{})).getValue(); const result = try (try self.interpreter.interpret(node, namespace, .{})).getValue();
try std.testing.expect(result.ty != .none); const val = self.interpreter.ip.indexToKey(result.index);
const ty = self.interpreter.ip.indexToKey(val.typeOf());
return KV{ return KV{
.ty = self.interpreter.ip.indexToKey(result.ty), .ty = ty,
.val = if (result.val == .none) null else self.interpreter.ip.indexToKey(result.val), .val = val,
}; };
} }
@ -428,8 +443,7 @@ fn testCall(
fn testExpr( fn testExpr(
expr: []const u8, expr: []const u8,
expected_ty: Key, expected: Key,
expected_val: ?Key,
) !void { ) !void {
const source = try std.fmt.allocPrint(allocator, const source = try std.fmt.allocPrint(allocator,
\\const foobarbaz = {s}; \\const foobarbaz = {s};
@ -441,34 +455,19 @@ fn testExpr(
const result = try context.interpret(context.findVar("foobarbaz")); const result = try context.interpret(context.findVar("foobarbaz"));
try expectEqualKey(context.interpreter.ip, expected_ty, result.ty); try expectEqualKey(context.interpreter.ip, expected, result.val);
if (expected_val) |expected| {
try expectEqualKey(context.interpreter.ip, expected, result.val);
}
} }
/// TODO refactor this code
fn expectEqualKey(ip: InternPool, expected: Key, actual: ?Key) !void { fn expectEqualKey(ip: InternPool, expected: Key, actual: ?Key) !void {
if (actual) |actual_key| { if (actual) |actual_key| {
if (expected.eql(actual_key)) return; if (!expected.eql(actual_key)) {
std.debug.print("expected `{}`, found `{}`\n", .{ expected.fmt(ip), actual_key.fmt(ip) });
if (expected.isType() and actual_key.isType()) { return error.TestExpectedEqual;
std.debug.print("expected type `{}`, found type `{}`\n", .{ expected.fmtType(ip), actual_key.fmtType(ip) });
} else if (expected.isType()) {
std.debug.print("expected type `{}`, found value ({})\n", .{ expected.fmtType(ip), actual_key });
} else if (actual_key.isType()) {
std.debug.print("expected value ({}), found type `{}`\n", .{ expected, actual_key.fmtType(ip) });
} else {
std.debug.print("expected value ({}), found value ({})\n", .{ expected, actual_key }); // TODO print value
} }
} else { } else {
if (expected.isType()) { std.debug.print("expected `{}`, found null\n", .{expected.fmt(ip)});
std.debug.print("expected type `{}`, found null\n", .{expected.fmtType(ip)}); return error.TestExpectedEqual;
} else {
std.debug.print("expected value ({}), found null\n", .{expected});
}
} }
return error.TestExpectedEqual;
} }
fn interpretReportErrors( fn interpretReportErrors(