@@ -6228,6 +6228,13 @@ fn checkUsedUnderscoreVariable(
62286228}
62296229
62306230fn checkScopeForUnusedVariables (self : * Self , scope : * const Scope ) std.mem.Allocator.Error ! void {
6231+ // Define the type for unused variables
6232+ const UnusedVar = struct { ident : base.Ident.Idx , region : Region };
6233+
6234+ // Collect all unused variables first so we can sort them
6235+ var unused_vars = std .ArrayList (UnusedVar ).init (self .env .gpa );
6236+ defer unused_vars .deinit ();
6237+
62316238 // Iterate through all identifiers in this scope
62326239 var iterator = scope .idents .iterator ();
62336240 while (iterator .next ()) | entry | {
@@ -6247,10 +6254,26 @@ fn checkScopeForUnusedVariables(self: *Self, scope: *const Scope) std.mem.Alloca
62476254 // Get the region for this pattern to provide good error location
62486255 const region = self .env .store .getPatternRegion (pattern_idx );
62496256
6250- // Report unused variable
6251- try self . env . pushDiagnostic ( Diagnostic { . unused_variable = .{
6257+ // Collect unused variable for sorting
6258+ try unused_vars . append ( .{
62526259 .ident = ident_idx ,
62536260 .region = region ,
6261+ });
6262+ }
6263+
6264+ // Sort unused variables by region (earlier in file first)
6265+ std .mem .sort (UnusedVar , unused_vars .items , {}, struct {
6266+ fn lessThan (_ : void , a : UnusedVar , b : UnusedVar ) bool {
6267+ // Compare by start offset (position in file)
6268+ return a .region .start .offset < b .region .start .offset ;
6269+ }
6270+ }.lessThan );
6271+
6272+ // Report unused variables in sorted order
6273+ for (unused_vars .items ) | unused | {
6274+ try self .env .pushDiagnostic (Diagnostic { .unused_variable = .{
6275+ .ident = unused .ident ,
6276+ .region = unused .region ,
62546277 } });
62556278 }
62566279}
@@ -8679,3 +8702,79 @@ test "hex literal parsing logic integration" {
86798702 try std .testing .expectEqual (tc .expected_value , u128_val );
86808703 }
86818704}
8705+
8706+ test "unused variables are sorted by region" {
8707+ const gpa = std .testing .allocator ;
8708+
8709+ // Create a test program with unused variables in non-alphabetical order
8710+ const source =
8711+ \\app [main!] { pf: platform "../basic-cli/main.roc" }
8712+ \\
8713+ \\func = |_| {
8714+ \\ zebra = 5 # Line 3 - should be reported first
8715+ \\ apple = 10 # Line 4 - should be reported second
8716+ \\ monkey = 15 # Line 5 - should be reported third
8717+ \\ used = 20 # Line 6 - this one is used
8718+ \\ used
8719+ \\}
8720+ \\
8721+ \\main! = |_| func({})
8722+ ;
8723+
8724+ var ctx = try ScopeTestContext .init (gpa );
8725+ defer ctx .deinit ();
8726+
8727+ // Parse the source
8728+ const ast = try parse .AST .parseFromStr (gpa , source , "test.roc" , & ctx .module_env .string_interner );
8729+ defer ast .deinit ();
8730+
8731+ // Canonicalize the AST
8732+ const parsed_module = ast .parsed_module ;
8733+ var self = try Self .initFromAST (parsed_module , & ctx .module_env , source );
8734+ try self .canonicalizeModule ();
8735+ defer self .deinit ();
8736+
8737+ // Check that we have unused variable diagnostics
8738+ var unused_var_diagnostics = std .ArrayList (struct {
8739+ ident : base.Ident.Idx ,
8740+ region : Region ,
8741+ }).init (gpa );
8742+ defer unused_var_diagnostics .deinit ();
8743+
8744+ // Collect all unused variable diagnostics
8745+ for (ctx .module_env .diagnostics .items ) | diagnostic | {
8746+ switch (diagnostic ) {
8747+ .unused_variable = > | data | {
8748+ try unused_var_diagnostics .append (.{
8749+ .ident = data .ident ,
8750+ .region = data .region ,
8751+ });
8752+ },
8753+ else = > continue ,
8754+ }
8755+ }
8756+
8757+ // We should have exactly 3 unused variables (zebra, apple, monkey)
8758+ try std .testing .expectEqual (@as (usize , 3 ), unused_var_diagnostics .items .len );
8759+
8760+ // Check that they are sorted by region (line number)
8761+ // The source positions should be in increasing order
8762+ var prev_offset : u32 = 0 ;
8763+ for (unused_var_diagnostics .items ) | diagnostic | {
8764+ const current_offset = diagnostic .region .start .offset ;
8765+
8766+ // Each unused variable should appear after the previous one in the source
8767+ try std .testing .expect (current_offset > prev_offset );
8768+ prev_offset = current_offset ;
8769+
8770+ // Also verify the names are in the expected order (zebra, apple, monkey)
8771+ const ident_text = ctx .module_env .idents .getText (diagnostic .ident );
8772+ if (unused_var_diagnostics .items [0 ].ident .idx == diagnostic .ident .idx ) {
8773+ try std .testing .expectEqualStrings ("zebra" , ident_text );
8774+ } else if (unused_var_diagnostics .items [1 ].ident .idx == diagnostic .ident .idx ) {
8775+ try std .testing .expectEqualStrings ("apple" , ident_text );
8776+ } else if (unused_var_diagnostics .items [2 ].ident .idx == diagnostic .ident .idx ) {
8777+ try std .testing .expectEqualStrings ("monkey" , ident_text );
8778+ }
8779+ }
8780+ }
0 commit comments