Skip to content

Commit e781a4f

Browse files
authored
Merge pull request #8406 from roc-lang/poly-numbers
Remove numeric primitives
2 parents 222f892 + 13b0d13 commit e781a4f

File tree

373 files changed

+9918
-11996
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

373 files changed

+9918
-11996
lines changed

.github/workflows/ci_zig.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121

2222
- name: zig lints
2323
run: |
24-
./ci/zig_lints.sh
24+
zig run ci/zig_lints.zig
2525
2626
zig build check-fmt
2727

ci/zig_lints.sh

Lines changed: 0 additions & 64 deletions
This file was deleted.

ci/zig_lints.zig

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
const std = @import("std");
2+
3+
const Allocator = std.mem.Allocator;
4+
const PathList = std.ArrayList([]u8);
5+
6+
const max_file_bytes: usize = 16 * 1024 * 1024;
7+
8+
const TermColor = struct {
9+
pub const red = "\x1b[0;31m";
10+
pub const green = "\x1b[0;32m";
11+
pub const reset = "\x1b[0m";
12+
};
13+
14+
pub fn main() !void {
15+
var gpa_impl = std.heap.GeneralPurposeAllocator(.{}){};
16+
defer _ = gpa_impl.deinit();
17+
const gpa = gpa_impl.allocator();
18+
19+
var stdout_buffer: [4096]u8 = undefined;
20+
var stdout_state = std.fs.File.stdout().writer(&stdout_buffer);
21+
const stdout = &stdout_state.interface;
22+
23+
var found_errors = false;
24+
25+
// Lint 1: Check for pub declarations without doc comments
26+
try stdout.print("Checking for pub declarations without doc comments...\n", .{});
27+
28+
var zig_files = PathList{};
29+
defer freePathList(&zig_files, gpa);
30+
31+
try walkTree(gpa, "src", &zig_files);
32+
33+
for (zig_files.items) |file_path| {
34+
const errors = try checkPubDocComments(gpa, file_path);
35+
defer gpa.free(errors);
36+
37+
if (errors.len > 0) {
38+
try stdout.print("{s}", .{errors});
39+
found_errors = true;
40+
}
41+
}
42+
43+
if (found_errors) {
44+
try stdout.print("\n", .{});
45+
try stdout.print("Please add doc comments to the spots listed above, they make the code easier to understand for everyone.\n", .{});
46+
try stdout.print("\n", .{});
47+
try stdout.flush();
48+
std.process.exit(1);
49+
}
50+
51+
// Lint 2: Check for top level comments in new Zig files
52+
try stdout.print("Checking for top level comments in new Zig files...\n", .{});
53+
54+
var new_zig_files = try getNewZigFiles(gpa);
55+
defer {
56+
for (new_zig_files.items) |path| {
57+
gpa.free(path);
58+
}
59+
new_zig_files.deinit(gpa);
60+
}
61+
62+
if (new_zig_files.items.len == 0) {
63+
try stdout.print("{s}[OK]{s} All lints passed!\n", .{ TermColor.green, TermColor.reset });
64+
try stdout.flush();
65+
return;
66+
}
67+
68+
var failed_files = PathList{};
69+
defer freePathList(&failed_files, gpa);
70+
71+
for (new_zig_files.items) |file_path| {
72+
if (!try fileHasTopLevelComment(gpa, file_path)) {
73+
try stdout.print("Error: {s} is missing top level comment (//!)\n", .{file_path});
74+
try failed_files.append(gpa, try gpa.dupe(u8, file_path));
75+
}
76+
}
77+
78+
if (failed_files.items.len > 0) {
79+
try stdout.print("\n", .{});
80+
try stdout.print("The following files are missing a top level comment:\n", .{});
81+
for (failed_files.items) |path| {
82+
try stdout.print(" {s}\n", .{path});
83+
}
84+
try stdout.print("\n", .{});
85+
try stdout.print("Add a //! comment BEFORE any other code that explains the purpose of the file.\n", .{});
86+
try stdout.flush();
87+
std.process.exit(1);
88+
}
89+
90+
try stdout.print("{s}[OK]{s} All lints passed!\n", .{ TermColor.green, TermColor.reset });
91+
try stdout.flush();
92+
}
93+
94+
fn walkTree(allocator: Allocator, dir_path: []const u8, zig_files: *PathList) !void {
95+
var dir = try std.fs.cwd().openDir(dir_path, .{ .iterate = true });
96+
defer dir.close();
97+
98+
var it = dir.iterate();
99+
while (try it.next()) |entry| {
100+
if (entry.kind == .sym_link) continue;
101+
102+
const next_path = try std.fs.path.join(allocator, &.{ dir_path, entry.name });
103+
104+
switch (entry.kind) {
105+
.directory => {
106+
// Skip .zig-cache directories
107+
if (std.mem.eql(u8, entry.name, ".zig-cache")) {
108+
allocator.free(next_path);
109+
continue;
110+
}
111+
defer allocator.free(next_path);
112+
try walkTree(allocator, next_path, zig_files);
113+
},
114+
.file => {
115+
if (std.mem.endsWith(u8, entry.name, ".zig")) {
116+
try zig_files.append(allocator, next_path);
117+
} else {
118+
allocator.free(next_path);
119+
}
120+
},
121+
else => allocator.free(next_path),
122+
}
123+
}
124+
}
125+
126+
fn checkPubDocComments(allocator: Allocator, file_path: []const u8) ![]u8 {
127+
const source = readSourceFile(allocator, file_path) catch |err| {
128+
// Skip files we can't read
129+
if (err == error.FileNotFound) return try allocator.dupe(u8, "");
130+
return err;
131+
};
132+
defer allocator.free(source);
133+
134+
var errors = std.ArrayList(u8){};
135+
errdefer errors.deinit(allocator);
136+
137+
var line_num: usize = 1;
138+
var prev_line: []const u8 = "";
139+
var lines = std.mem.splitScalar(u8, source, '\n');
140+
141+
while (lines.next()) |line| {
142+
defer {
143+
prev_line = line;
144+
line_num += 1;
145+
}
146+
147+
// Check if line starts with "pub " (no leading whitespace - only top-level declarations)
148+
if (!std.mem.startsWith(u8, line, "pub ")) continue;
149+
150+
// Check if previous line is a doc comment (allow indented doc comments)
151+
const prev_trimmed = std.mem.trimLeft(u8, prev_line, " \t");
152+
if (std.mem.startsWith(u8, prev_trimmed, "///")) continue;
153+
154+
// Skip exceptions: init, deinit, @import, and pub const re-exports
155+
// Note: "pub.*fn init\(" in bash matches "init" anywhere in function name
156+
if (std.mem.indexOf(u8, line, "fn init") != null) continue;
157+
if (std.mem.indexOf(u8, line, "fn deinit") != null) continue;
158+
if (std.mem.indexOf(u8, line, "@import") != null) continue;
159+
160+
// Check for pub const re-exports (e.g., "pub const Foo = bar.Baz;")
161+
if (isReExport(line)) continue;
162+
163+
try errors.writer(allocator).print("{s}:{d}: pub declaration without doc comment `///`\n", .{ file_path, line_num });
164+
}
165+
166+
return errors.toOwnedSlice(allocator);
167+
}
168+
169+
fn isReExport(line: []const u8) bool {
170+
// Match pattern: pub const X = lowercase.something;
171+
// This detects re-exports like "pub const Foo = bar.Baz;"
172+
if (!std.mem.startsWith(u8, line, "pub const ")) return false;
173+
174+
// Find the '=' sign
175+
const eq_pos = std.mem.indexOf(u8, line, "=") orelse return false;
176+
const after_eq = std.mem.trimLeft(u8, line[eq_pos + 1 ..], " \t");
177+
178+
// Check if it starts with a lowercase letter (module reference)
179+
if (after_eq.len == 0) return false;
180+
const first_char = after_eq[0];
181+
if (first_char < 'a' or first_char > 'z') return false;
182+
183+
// Check if it contains a dot and ends with semicolon (but not a function call)
184+
if (std.mem.indexOf(u8, after_eq, ".") == null) return false;
185+
if (std.mem.indexOf(u8, after_eq, "(") != null) return false;
186+
if (!std.mem.endsWith(u8, std.mem.trimRight(u8, after_eq, " \t"), ";")) return false;
187+
188+
return true;
189+
}
190+
191+
fn getNewZigFiles(allocator: Allocator) !PathList {
192+
var result = PathList{};
193+
errdefer {
194+
for (result.items) |path| {
195+
allocator.free(path);
196+
}
197+
result.deinit(allocator);
198+
}
199+
200+
// Run git diff to get new files
201+
var child = std.process.Child.init(&.{ "git", "diff", "--name-only", "--diff-filter=A", "origin/main", "HEAD", "--", "src/" }, allocator);
202+
child.stdout_behavior = .Pipe;
203+
child.stderr_behavior = .Ignore;
204+
205+
_ = child.spawn() catch {
206+
// Git not available or not in a repo - return empty list
207+
return result;
208+
};
209+
210+
const stdout = child.stdout orelse return result;
211+
const output = stdout.readToEndAlloc(allocator, max_file_bytes) catch return result;
212+
defer allocator.free(output);
213+
214+
const term = child.wait() catch return result;
215+
if (term.Exited != 0) return result;
216+
217+
// Parse output line by line
218+
var lines = std.mem.splitScalar(u8, output, '\n');
219+
while (lines.next()) |line| {
220+
if (line.len == 0) continue;
221+
if (!std.mem.endsWith(u8, line, ".zig")) continue;
222+
223+
try result.append(allocator, try allocator.dupe(u8, line));
224+
}
225+
226+
return result;
227+
}
228+
229+
fn fileHasTopLevelComment(allocator: Allocator, file_path: []const u8) !bool {
230+
const source = try readSourceFile(allocator, file_path);
231+
defer allocator.free(source);
232+
233+
return std.mem.indexOf(u8, source, "//!") != null;
234+
}
235+
236+
fn readSourceFile(allocator: Allocator, path: []const u8) ![:0]u8 {
237+
return try std.fs.cwd().readFileAllocOptions(
238+
allocator,
239+
path,
240+
max_file_bytes,
241+
null,
242+
std.mem.Alignment.of(u8),
243+
0,
244+
);
245+
}
246+
247+
fn freePathList(list: *PathList, allocator: Allocator) void {
248+
for (list.items) |path| {
249+
allocator.free(path);
250+
}
251+
list.deinit(allocator);
252+
}

src/base/CommonEnv.zig

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,9 @@ pub fn serialize(
8484
return @constCast(offset_self);
8585
}
8686

87-
/// Serialized representation of ModuleEnv
88-
pub const Serialized = struct {
87+
/// Serialized representation of CommonEnv
88+
/// Uses extern struct to guarantee consistent field layout across optimization levels.
89+
pub const Serialized = extern struct {
8990
idents: Ident.Store.Serialized,
9091
strings: StringLiteral.Store.Serialized,
9192
exposed_items: ExposedItems.Serialized,

src/base/DataSpan.zig

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
//! Just a small struct to take a span of data in an array
22

3-
const DataSpan = @This();
3+
/// DataSpan is used for serialization, so it must be extern struct for consistent layout.
4+
pub const DataSpan = extern struct {
5+
start: u32,
6+
len: u32,
47

5-
start: u32,
6-
len: u32,
8+
/// Creates an empty DataSpan with zero start and zero length.
9+
pub fn empty() DataSpan {
10+
return DataSpan{ .start = 0, .len = 0 };
11+
}
712

8-
/// Creates an empty DataSpan with zero start and zero length.
9-
pub fn empty() DataSpan {
10-
return DataSpan{ .start = 0, .len = 0 };
11-
}
13+
/// Creates a DataSpan with the specified start position and length.
14+
pub fn init(start: u32, len: u32) DataSpan {
15+
return DataSpan{ .start = start, .len = len };
16+
}
1217

13-
/// Creates a DataSpan with the specified start position and length.
14-
pub fn init(start: u32, len: u32) DataSpan {
15-
return DataSpan{ .start = start, .len = len };
16-
}
17-
18-
/// Converts this DataSpan into a type that contains a span field.
19-
/// This is useful for creating wrapper types around DataSpan.
20-
pub fn as(self: DataSpan, comptime T: type) T {
21-
return @as(T, .{ .span = self });
22-
}
18+
/// Converts this DataSpan into a type that contains a span field.
19+
/// This is useful for creating wrapper types around DataSpan.
20+
pub fn as(self: DataSpan, comptime T: type) T {
21+
return @as(T, .{ .span = self });
22+
}
23+
};

src/base/Ident.zig

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ pub const FROM_INT_DIGITS_METHOD_NAME = "from_int_digits";
2222
pub const FROM_DEC_DIGITS_METHOD_NAME = "from_dec_digits";
2323
/// Method name for addition - used by + operator desugaring
2424
pub const PLUS_METHOD_NAME = "plus";
25+
/// Method name for negation - used by unary - operator desugaring
26+
pub const NEGATE_METHOD_NAME = "negate";
2527

2628
/// The original text of the identifier.
2729
raw_text: []const u8,
@@ -103,7 +105,8 @@ pub const Store = struct {
103105
next_unique_name: u32 = 0,
104106

105107
/// Serialized representation of an Ident.Store
106-
pub const Serialized = struct {
108+
/// Uses extern struct to guarantee consistent field layout across optimization levels.
109+
pub const Serialized = extern struct {
107110
interner: SmallStringInterner.Serialized,
108111
attributes: collections.SafeList(Attributes).Serialized,
109112
next_unique_name: u32,

0 commit comments

Comments
 (0)