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