From a363ac8c6bc4be5f393cf28b441135959fbd900d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Sun, 15 Mar 2026 18:31:31 -0700 Subject: [PATCH] Calculate comptime integer arithmetic --- src/analyser/InternPool.zig | 9 ++ src/analysis.zig | 222 ++++++++++++++++++++++++----- tests/analysis/integer_literal.zig | 138 ++++++++++++++++-- tests/lsp_features/hover.zig | 6 +- 4 files changed, 328 insertions(+), 47 deletions(-) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index d685551e4..8e8f62299 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -3846,6 +3846,15 @@ pub fn toInt(ip: *InternPool, val: Index, comptime T: type) ?T { }; } +pub fn toBigInt(ip: *InternPool, gpa: Allocator, val: Index) !?std.math.big.int.Managed { + return switch (ip.indexToKey(val)) { + .int_u64_value => |int_value| try .initSet(gpa, int_value.int), + .int_i64_value => |int_value| try .initSet(gpa, int_value.int), + .int_big_value => |int_value| try int_value.getConst(ip).toManaged(gpa), + else => try .initSet(gpa, ip.toInt(val, i64) orelse return null), + }; +} + pub fn getBigInt(ip: *InternPool, ty: Index, int: std.math.big.int.Const) Allocator.Error!Index { assert(ip.isType(ty)); return try ip.get(.{ diff --git a/src/analysis.zig b/src/analysis.zig index 60f68acd4..2706c33f5 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -1464,6 +1464,157 @@ fn resolveIntegerLiteral(analyser: *Analyser, comptime T: type, options: Resolve return analyser.ip.toInt(ip_index, T); } +const IntInfo = union(enum) { + comptime_int, + fixed_int: std.builtin.Type.Int, +}; + +fn intInfo(analyser: *Analyser, ty: InternPool.Index) ?IntInfo { + const type_tag = analyser.ip.zigTypeTag(ty) orelse return null; + return switch (type_tag) { + .comptime_int => .comptime_int, + .int => .{ .fixed_int = analyser.ip.intInfo(ty, builtin.target) }, + else => null, + }; +} + +fn resolveUnaryIntegerExpression( + analyser: *Analyser, + tag: std.zig.Ast.Node.Tag, + instance: Type, +) !?Type { + const index = instance.ipIndex() orelse return null; + const ty = analyser.ip.typeOf(index); + const int_info = analyser.intInfo(ty) orelse return null; + var result = try analyser.ip.toBigInt(analyser.gpa, index) orelse return null; + defer result.deinit(); + switch (tag) { + .bit_not => switch (int_info) { + .comptime_int => return null, + .fixed_int => |info| _ = try result.bitNotWrap(&result, info.signedness, info.bits), + }, + .negation => result.negate(), + .negation_wrap => switch (int_info) { + .comptime_int => result.negate(), + // std.math.big.int does not have a negateWrap method + .fixed_int => |info| switch (info.signedness) { + .signed => { + result.negate(); + if (!result.fitsInTwosComp(.signed, info.bits)) + result.negate(); + }, + .unsigned => if (!result.eqlZero()) { + var temp: std.math.big.int.Managed = try .initSet(analyser.gpa, 1); + defer temp.deinit(); + try temp.shiftLeft(&temp, info.bits); + try result.sub(&temp, &result); + }, + }, + }, + else => unreachable, + } + return try analyser.resolveIntegerExpressionResult(ty, result.toConst()); +} + +fn resolveBinaryIntegerExpression( + analyser: *Analyser, + tag: std.zig.Ast.Node.Tag, + instance: Type, + lhs_instance: Type, + rhs_instance: Type, +) !?Type { + const lhs_index = lhs_instance.ipIndex() orelse return null; + const rhs_index = rhs_instance.ipIndex() orelse return null; + const type_of = try instance.typeOf(analyser); + const ty = type_of.ipIndex() orelse return null; + const int_info = analyser.intInfo(ty) orelse return null; + var lhs = try analyser.ip.toBigInt(analyser.gpa, lhs_index) orelse return null; + defer lhs.deinit(); + var rhs = try analyser.ip.toBigInt(analyser.gpa, rhs_index) orelse return null; + defer rhs.deinit(); + var result: std.math.big.int.Managed = try .init(analyser.gpa); + defer result.deinit(); + switch (tag) { + .mul => try result.mul(&lhs, &rhs), + .div => { + var temp: std.math.big.int.Managed = try .init(analyser.gpa); + defer temp.deinit(); + try result.divTrunc(&temp, &lhs, &rhs); + }, + .mod => { + var temp: std.math.big.int.Managed = try .init(analyser.gpa); + defer temp.deinit(); + try temp.divTrunc(&result, &lhs, &rhs); + }, + .add => try result.add(&lhs, &rhs), + .sub => try result.sub(&lhs, &rhs), + .shl => try result.shiftLeft(&lhs, rhs.toInt(usize) catch return null), + .shr => try result.shiftRight(&lhs, rhs.toInt(usize) catch return null), + .bit_and => try result.bitAnd(&lhs, &rhs), + .bit_xor => try result.bitXor(&lhs, &rhs), + .bit_or => try result.bitOr(&lhs, &rhs), + .add_wrap, + .sub_wrap, + .mul_wrap, + .add_sat, + .sub_sat, + .mul_sat, + .shl_sat, + => switch (int_info) { + .comptime_int => switch (tag) { + .add_wrap, .add_sat => try result.add(&lhs, &rhs), + .sub_wrap, .sub_sat => try result.sub(&lhs, &rhs), + .mul_wrap, .mul_sat => try result.mul(&lhs, &rhs), + .shl_sat => try result.shiftLeft(&lhs, rhs.toInt(usize) catch return null), + else => unreachable, + }, + .fixed_int => |info| switch (tag) { + .add_wrap => _ = try result.addWrap(&lhs, &rhs, info.signedness, info.bits), + .sub_wrap => _ = try result.subWrap(&lhs, &rhs, info.signedness, info.bits), + .mul_wrap => _ = try result.mulWrap(&lhs, &rhs, info.signedness, info.bits), + .add_sat => try result.addSat(&lhs, &rhs, info.signedness, info.bits), + .sub_sat => try result.subSat(&lhs, &rhs, info.signedness, info.bits), + // std.math.big.int does not have a mulSat method + .mul_sat => { + try result.mul(&lhs, &rhs); + try result.saturate(&result, info.signedness, info.bits); + }, + .shl_sat => try result.shiftLeftSat(&lhs, rhs.toInt(usize) catch return null, info.signedness, info.bits), + else => unreachable, + }, + }, + else => unreachable, + } + return try analyser.resolveIntegerExpressionResult(ty, result.toConst()); +} + +fn resolveIntegerExpressionResult( + analyser: *Analyser, + ty: InternPool.Index, + result: std.math.big.int.Const, +) !?Type { + const int_info = analyser.intInfo(ty) orelse return null; + switch (int_info) { + .comptime_int => {}, + .fixed_int => |info| { + if (!result.fitsInTwosComp(info.signedness, info.bits)) { + return null; + } + }, + } + const index = index: { + if (result.positive) blk: { + const int = result.toInt(u64) catch break :blk; + break :index try analyser.ip.get(.{ .int_u64_value = .{ .ty = ty, .int = int } }); + } else blk: { + const int = result.toInt(i64) catch break :blk; + break :index try analyser.ip.get(.{ .int_i64_value = .{ .ty = ty, .int = int } }); + } + break :index try analyser.ip.getBigInt(ty, result); + }; + return Type.fromIP(analyser, ty, index); +} + const primitives: std.StaticStringMap(InternPool.Index) = .initComptime(.{ .{ "anyerror", .anyerror_type }, .{ "anyframe", .anyframe_type }, @@ -1982,8 +2133,9 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, options: ResolveOptions) Error const node = node_handle.node; const handle = node_handle.handle; const tree = &handle.tree; + const node_tag = tree.nodeTag(node); - switch (tree.nodeTag(node)) { + switch (node_tag) { .global_var_decl, .local_var_decl, .simple_var_decl, @@ -2754,7 +2906,7 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, options: ResolveOptions) Error => { const ty = try analyser.resolveTypeOfNodeInternal(.of(tree.nodeData(node).node, handle)) orelse return null; if (ty.is_type_val) return null; - return ty.withoutIPIndex(analyser); + return try analyser.resolveUnaryIntegerExpression(node_tag, ty) orelse ty.withoutIPIndex(analyser); }, .multiline_string_literal => { @@ -2899,63 +3051,69 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, options: ResolveOptions) Error .bit_or, => { const lhs, const rhs = tree.nodeData(node).node_and_node; - var lhs_ty = try analyser.resolveTypeOfNodeInternal(.of(lhs, handle)) orelse return null; - if (lhs_ty.is_type_val) return null; - var rhs_ty = try analyser.resolveTypeOfNodeInternal(.of(rhs, handle)) orelse return null; - if (rhs_ty.is_type_val) return null; - lhs_ty = lhs_ty.withoutIPIndex(analyser); - rhs_ty = rhs_ty.withoutIPIndex(analyser); - return analyser.resolvePeerTypes(lhs_ty, rhs_ty); + const lhs_instance = try analyser.resolveTypeOfNodeInternal(.of(lhs, handle)) orelse return null; + if (lhs_instance.is_type_val) return null; + const rhs_instance = try analyser.resolveTypeOfNodeInternal(.of(rhs, handle)) orelse return null; + if (rhs_instance.is_type_val) return null; + const lhs_no_value = lhs_instance.withoutIPIndex(analyser); + const rhs_no_value = rhs_instance.withoutIPIndex(analyser); + const instance = try analyser.resolvePeerTypes(lhs_no_value, rhs_no_value) orelse return null; + return try analyser.resolveBinaryIntegerExpression(node_tag, instance, lhs_instance, rhs_instance) orelse return instance; }, .add => { const lhs, const rhs = tree.nodeData(node).node_and_node; - var lhs_ty = try analyser.resolveTypeOfNodeInternal(.of(lhs, handle)) orelse return null; - if (lhs_ty.is_type_val) return null; - var rhs_ty = try analyser.resolveTypeOfNodeInternal(.of(rhs, handle)) orelse return null; - if (rhs_ty.is_type_val) return null; - lhs_ty = lhs_ty.withoutIPIndex(analyser); - rhs_ty = rhs_ty.withoutIPIndex(analyser); - if (lhs_ty.pointerSize(analyser)) |lhs_size| { + const lhs_instance = try analyser.resolveTypeOfNodeInternal(.of(lhs, handle)) orelse return null; + if (lhs_instance.is_type_val) return null; + const rhs_instance = try analyser.resolveTypeOfNodeInternal(.of(rhs, handle)) orelse return null; + if (rhs_instance.is_type_val) return null; + const lhs_no_value = lhs_instance.withoutIPIndex(analyser); + const rhs_no_value = rhs_instance.withoutIPIndex(analyser); + if (lhs_no_value.pointerSize(analyser)) |lhs_size| { return switch (lhs_size) { - .many, .c => lhs_ty, + .many, .c => lhs_no_value, else => null, }; } - return try analyser.resolvePeerTypes(lhs_ty, rhs_ty); + const instance = try analyser.resolvePeerTypes(lhs_no_value, rhs_no_value) orelse return null; + return try analyser.resolveBinaryIntegerExpression(node_tag, instance, lhs_instance, rhs_instance) orelse return instance; }, .sub => { const lhs, const rhs = tree.nodeData(node).node_and_node; - var lhs_ty = try analyser.resolveTypeOfNodeInternal(.of(lhs, handle)) orelse return null; - if (lhs_ty.is_type_val) return null; - var rhs_ty = try analyser.resolveTypeOfNodeInternal(.of(rhs, handle)) orelse return null; - if (rhs_ty.is_type_val) return null; - lhs_ty = lhs_ty.withoutIPIndex(analyser); - rhs_ty = rhs_ty.withoutIPIndex(analyser); - if (lhs_ty.pointerSize(analyser)) |lhs_size| { - if (rhs_ty.pointerSize(analyser)) |rhs_size| { + const lhs_instance = try analyser.resolveTypeOfNodeInternal(.of(lhs, handle)) orelse return null; + if (lhs_instance.is_type_val) return null; + const rhs_instance = try analyser.resolveTypeOfNodeInternal(.of(rhs, handle)) orelse return null; + if (rhs_instance.is_type_val) return null; + const lhs_no_value = lhs_instance.withoutIPIndex(analyser); + const rhs_no_value = rhs_instance.withoutIPIndex(analyser); + if (lhs_no_value.pointerSize(analyser)) |lhs_size| { + if (rhs_no_value.pointerSize(analyser)) |rhs_size| { if (lhs_size == .slice) return null; if (rhs_size == .slice) return null; return Type.fromIP(analyser, .usize_type, null); } else { return switch (lhs_size) { - .many, .c => lhs_ty, + .many, .c => lhs_no_value, else => null, }; } } - return try analyser.resolvePeerTypes(lhs_ty, rhs_ty); + const instance = try analyser.resolvePeerTypes(lhs_no_value, rhs_no_value) orelse return null; + return try analyser.resolveBinaryIntegerExpression(node_tag, instance, lhs_instance, rhs_instance) orelse return instance; }, .shl, .shl_sat, .shr, => { - const lhs, _ = tree.nodeData(node).node_and_node; - const lhs_ty = try analyser.resolveTypeOfNodeInternal(.of(lhs, handle)) orelse return null; - if (lhs_ty.is_type_val) return null; - return lhs_ty.withoutIPIndex(analyser); + const lhs, const rhs = tree.nodeData(node).node_and_node; + const lhs_instance = try analyser.resolveTypeOfNodeInternal(.of(lhs, handle)) orelse return null; + if (lhs_instance.is_type_val) return null; + const lhs_no_value = lhs_instance.withoutIPIndex(analyser); + const rhs_instance = try analyser.resolveTypeOfNodeInternal(.of(rhs, handle)) orelse return lhs_no_value; + if (rhs_instance.is_type_val) return lhs_no_value; + return try analyser.resolveBinaryIntegerExpression(node_tag, lhs_no_value, lhs_instance, rhs_instance) orelse return lhs_no_value; }, .array_mult => { diff --git a/tests/analysis/integer_literal.zig b/tests/analysis/integer_literal.zig index eeeee77a9..8db4c55ec 100644 --- a/tests/analysis/integer_literal.zig +++ b/tests/analysis/integer_literal.zig @@ -1,36 +1,75 @@ const comptime_integer = 42; // ^^^^^^^^^^^^^^^^ (comptime_int)(42) -const comptime_plus = 2 + 3; -// ^^^^^^^^^^^^^ (comptime_int)((unknown value)) TODO this should be `5` +const comptime_add = 2 + 3; +// ^^^^^^^^^^^^ (comptime_int)(5) + +const comptime_add_sat = 2 +| 3; +// ^^^^^^^^^^^^^^^^ (comptime_int)(5) + +const comptime_add_wrap = 2 +% 3; +// ^^^^^^^^^^^^^^^^^ (comptime_int)(5) const comptime_sub = 2 - 3; -// ^^^^^^^^^^^^ (comptime_int)((unknown value)) TODO this should be `-1` +// ^^^^^^^^^^^^ (comptime_int)(-1) + +const comptime_sub_sat = 2 -| 3; +// ^^^^^^^^^^^^^^^^ (comptime_int)(-1) + +const comptime_sub_wrap = 2 -% 3; +// ^^^^^^^^^^^^^^^^^ (comptime_int)(-1) const comptime_mul = 2 * 3; -// ^^^^^^^^^^^^ (comptime_int)((unknown value)) TODO this should be `6` +// ^^^^^^^^^^^^ (comptime_int)(6) + +const comptime_mul_sat = 2 *| 3; +// ^^^^^^^^^^^^^^^^ (comptime_int)(6) + +const comptime_mul_wrap = 2 *% 3; +// ^^^^^^^^^^^^^^^^^ (comptime_int)(6) const comptime_div = 2 / 3; -// ^^^^^^^^^^^^ (comptime_int)((unknown value)) TODO this should be `0` +// ^^^^^^^^^^^^ (comptime_int)(0) + +const comptime_mod = 2 % 3; +// ^^^^^^^^^^^^ (comptime_int)(2) const comptime_and = 2 & 3; -// ^^^^^^^^^^^^ (comptime_int)((unknown value)) TODO this should be `2` +// ^^^^^^^^^^^^ (comptime_int)(2) + +const comptime_or = 2 | 3; +// ^^^^^^^^^^^ (comptime_int)(3) + +const comptime_xor = 2 ^ 3; +// ^^^^^^^^^^^^ (comptime_int)(1) const comptime_shl = 2 << 3; -// ^^^^^^^^^^^^ (comptime_int)((unknown value)) TODO this should be `16` +// ^^^^^^^^^^^^ (comptime_int)(16) + +const comptime_shl_sat = 2 <<| 3; +// ^^^^^^^^^^^^^^^^ (comptime_int)(16) + +const comptime_shr = 2 >> 3; +// ^^^^^^^^^^^^ (comptime_int)(0) const one_plus_one = 1 + 1; -// ^^^^^^^^^^^^ (comptime_int)((unknown value)) TODO this should be `2` +// ^^^^^^^^^^^^ (comptime_int)(2) const negation_one = -1; -// ^^^^^^^^^^^^ (comptime_int)((unknown value)) TODO this should be `-1` +// ^^^^^^^^^^^^ (comptime_int)(-1) const negation_wrap_one = -%1; -// ^^^^^^^^^^^^^^^^^ (comptime_int)((unknown value)) TODO this should be `-1` +// ^^^^^^^^^^^^^^^^^ (comptime_int)(-1) const bit_not_one = ~1; // ^^^^^^^^^^^ (comptime_int)((unknown value)) +const negative_three_div_two = -3 / 2; +// ^^^^^^^^^^^^^^^^^^^^^^ (comptime_int)(-1) + +const three_div_negative_two = 3 / -2; +// ^^^^^^^^^^^^^^^^^^^^^^ (comptime_int)(-1) + const const_u8: u8 = 42; // ^^^^^^^^ (u8)(42) @@ -50,10 +89,85 @@ var var_as_u8 = @as(u8, 42); // ^^^^^^^^^ (u8)((unknown value)) const comptime_plus_u8 = 2 + @as(u8, 3); -// ^^^^^^^^^^^^^^^^ (u8)((unknown value)) TODO this should be `5` +// ^^^^^^^^^^^^^^^^ (u8)(5) const u8_plus_comptime = @as(u8, 2) + 3; -// ^^^^^^^^^^^^^^^^ (u8)((unknown value)) TODO this should be `5` +// ^^^^^^^^^^^^^^^^ (u8)(5) + +const u4_add = @as(u4, 2) + 3; +// ^^^^^^ (u4)(5) + +const u4_add_sat = @as(u4, 2) +| 15; +// ^^^^^^^^^^ (u4)(15) + +const u4_add_wrap = @as(u4, 2) +% 15; +// ^^^^^^^^^^^ (u4)(1) + +const u4_sub = @as(u4, 2) - 3; +// ^^^^^^ (u4)((unknown value)) + +const u4_sub_sat = @as(u4, 2) -| 3; +// ^^^^^^^^^^ (u4)(0) + +const u4_sub_wrap = @as(u4, 2) -% 3; +// ^^^^^^^^^^^ (u4)(15) + +const u4_mul = @as(u4, 2) * 3; +// ^^^^^^ (u4)(6) + +const u4_mul_sat = @as(u4, 2) *| 8; +// ^^^^^^^^^^ (u4)(15) + +const u4_mul_wrap = @as(u4, 2) *% 8; +// ^^^^^^^^^^^ (u4)(0) + +const u4_div = @as(u4, 2) / 3; +// ^^^^^^ (u4)(0) + +const u4_mod = @as(u4, 2) % 3; +// ^^^^^^ (u4)(2) + +const u4_and = @as(u4, 2) & 3; +// ^^^^^^ (u4)(2) + +const u4_or = @as(u4, 2) | 3; +// ^^^^^ (u4)(3) + +const u4_xor = @as(u4, 2) ^ 3; +// ^^^^^^ (u4)(1) + +const u4_shl = @as(u4, 2) << 2; +// ^^^^^^ (u4)(8) + +const u4_shl_sat = @as(u4, 2) <<| 3; +// ^^^^^^^^^^ (u4)(15) + +const u4_shr = @as(u4, 2) >> 3; +// ^^^^^^ (u4)(0) + +const u4_bit_not = ~@as(u4, 2); +// ^^^^^^^^^^ (u4)(13) + +const u4_negation = -@as(u4, 2); +// ^^^^^^^^^^^ (u4)((unknown value)) + +const u4_negation_wrap = -%@as(u4, 2); +// ^^^^^^^^^^^^^^^^ (u4)(14) + +const u4_negation_wrap_zero = -%@as(u4, 0); +// ^^^^^^^^^^^^^^^^^^^^^ (u4)(0) + +const i4_bit_not = ~@as(i4, 2); +// ^^^^^^^^^^ (i4)(-3) + +const i4_negation = -@as(i4, 2); +// ^^^^^^^^^^^ (i4)(-2) + +const i4_negation_wrap = -%@as(i4, 2); +// ^^^^^^^^^^^^^^^^ (i4)(-2) + +const i4_negation_wrap_min = -%@as(i4, -8); +// ^^^^^^^^^^^^^^^^^^^^ (i4)(-8) comptime { @compileLog(comptime_div); diff --git a/tests/lsp_features/hover.zig b/tests/lsp_features/hover.zig index 68e1c1c1e..c6eb8ee62 100644 --- a/tests/lsp_features/hover.zig +++ b/tests/lsp_features/hover.zig @@ -1053,13 +1053,13 @@ test "either type instances - big" { test "var decl comments" { try testHover( \\///this is a comment - \\const foo = 0 + 0; + \\const foo = 0; , \\```zig - \\const foo = 0 + 0 + \\const foo = 0 \\``` \\```zig - \\(comptime_int) + \\(comptime_int = 0) \\``` \\ \\this is a comment