| ... | @@ -4076,17 +4076,34 @@ pub fn scanNamespace( | ... | @@ -4076,17 +4076,34 @@ pub fn scanNamespace( |
| 4076 | const gpa = zcu.gpa; | 4076 | const gpa = zcu.gpa; |
| 4077 | const namespace = zcu.namespacePtr(namespace_index); | 4077 | const namespace = zcu.namespacePtr(namespace_index); |
| 4078 | | 4078 | |
| | 4079 | // For incremental updates, `scanDecl` wants to look up existing decls by their ZIR index rather |
| | 4080 | // than their name. We'll build an efficient mapping now, then discard the current `decls`. |
| | 4081 | var existing_by_inst: std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, Decl.Index) = .{}; |
| | 4082 | defer existing_by_inst.deinit(gpa); |
| | 4083 | |
| | 4084 | try existing_by_inst.ensureTotalCapacity(namespace.decls.count()); |
| | 4085 | |
| | 4086 | for (namespace.decls.keys()) |decl_index| { |
| | 4087 | const decl = zcu.declPtr(decl_index); |
| | 4088 | existing_by_inst.putAssumeCapacityNoClobber(decl.zir_decl_index.unwrap().?, decl_index); |
| | 4089 | } |
| | 4090 | |
| 4079 | var seen_decls: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{}; | 4091 | var seen_decls: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{}; |
| 4080 | defer seen_decls.deinit(gpa); | 4092 | defer seen_decls.deinit(gpa); |
| 4081 | | 4093 | |
| 4082 | try zcu.comp.work_queue.ensureUnusedCapacity(decls.len); | 4094 | try zcu.comp.work_queue.ensureUnusedCapacity(decls.len); |
| | 4095 | |
| | 4096 | namespace.decls.clearRetainingCapacity(); |
| 4083 | try namespace.decls.ensureTotalCapacity(gpa, decls.len); | 4097 | try namespace.decls.ensureTotalCapacity(gpa, decls.len); |
| 4084 | | 4098 | |
| | 4099 | namespace.usingnamespace_set.clearRetainingCapacity(); |
| | 4100 | |
| 4085 | var scan_decl_iter: ScanDeclIter = .{ | 4101 | var scan_decl_iter: ScanDeclIter = .{ |
| 4086 | .zcu = zcu, | 4102 | .zcu = zcu, |
| 4087 | .namespace_index = namespace_index, | 4103 | .namespace_index = namespace_index, |
| 4088 | .parent_decl = parent_decl, | 4104 | .parent_decl = parent_decl, |
| 4089 | .seen_decls = &seen_decls, | 4105 | .seen_decls = &seen_decls, |
| | 4106 | .existing_by_inst = &existing_by_inst, |
| 4090 | .pass = .named, | 4107 | .pass = .named, |
| 4091 | }; | 4108 | }; |
| 4092 | for (decls) |decl_inst| { | 4109 | for (decls) |decl_inst| { |
| ... | @@ -4118,6 +4135,7 @@ const ScanDeclIter = struct { | ... | @@ -4118,6 +4135,7 @@ const ScanDeclIter = struct { |
| 4118 | namespace_index: Namespace.Index, | 4135 | namespace_index: Namespace.Index, |
| 4119 | parent_decl: *Decl, | 4136 | parent_decl: *Decl, |
| 4120 | seen_decls: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void), | 4137 | seen_decls: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void), |
| | 4138 | existing_by_inst: *const std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, Decl.Index), |
| 4121 | /// Decl scanning is run in two passes, so that we can detect when a generated | 4139 | /// Decl scanning is run in two passes, so that we can detect when a generated |
| 4122 | /// name would clash with an explicit name and use a different one. | 4140 | /// name would clash with an explicit name and use a different one. |
| 4123 | pass: enum { named, unnamed }, | 4141 | pass: enum { named, unnamed }, |
| ... | @@ -4222,72 +4240,85 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void | ... | @@ -4222,72 +4240,85 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void |
| 4222 | }, | 4240 | }, |
| 4223 | }; | 4241 | }; |
| 4224 | | 4242 | |
| 4225 | if (kind == .@"usingnamespace") try namespace.usingnamespace_set.ensureUnusedCapacity(gpa, 1); | 4243 | switch (kind) { |
| | 4244 | .@"usingnamespace" => try namespace.usingnamespace_set.ensureUnusedCapacity(gpa, 1), |
| | 4245 | .@"test" => try zcu.test_functions.ensureUnusedCapacity(gpa, 1), |
| | 4246 | else => {}, |
| | 4247 | } |
| 4226 | | 4248 | |
| 4227 | const tracked_inst = try ip.trackZir(gpa, iter.parent_decl.getFileScope(zcu), decl_inst); | 4249 | const tracked_inst = try ip.trackZir(gpa, iter.parent_decl.getFileScope(zcu), decl_inst); |
| 4228 | | 4250 | |
| 4229 | // We create a Decl for it regardless of analysis status. | 4251 | // We create a Decl for it regardless of analysis status. |
| 4230 | const gop = try namespace.decls.getOrPutContextAdapted( | 4252 | |
| 4231 | gpa, | 4253 | const prev_exported, const decl_index = if (iter.existing_by_inst.get(tracked_inst)) |decl_index| decl_index: { |
| 4232 | decl_name, | 4254 | // We need only update this existing Decl. |
| 4233 | DeclAdapter{ .zcu = zcu }, | 4255 | const decl = zcu.declPtr(decl_index); |
| 4234 | Namespace.DeclContext{ .zcu = zcu }, | 4256 | const was_exported = decl.is_exported; |
| 4235 | ); | 4257 | assert(decl.kind == kind); // ZIR tracking should preserve this |
| 4236 | const comp = zcu.comp; | 4258 | assert(decl.alive); |
| 4237 | if (!gop.found_existing) { | 4259 | decl.name = decl_name; |
| | 4260 | decl.src_node = decl_node; |
| | 4261 | decl.src_line = line; |
| | 4262 | decl.is_pub = declaration.flags.is_pub; |
| | 4263 | decl.is_exported = declaration.flags.is_export; |
| | 4264 | break :decl_index .{ was_exported, decl_index }; |
| | 4265 | } else decl_index: { |
| | 4266 | // Create and set up a new Decl. |
| 4238 | const new_decl_index = try zcu.allocateNewDecl(namespace_index, decl_node); | 4267 | const new_decl_index = try zcu.allocateNewDecl(namespace_index, decl_node); |
| 4239 | const new_decl = zcu.declPtr(new_decl_index); | 4268 | const new_decl = zcu.declPtr(new_decl_index); |
| 4240 | new_decl.kind = kind; | 4269 | new_decl.kind = kind; |
| 4241 | new_decl.name = decl_name; | 4270 | new_decl.name = decl_name; |
| 4242 | if (kind == .@"usingnamespace") { | | |
| 4243 | namespace.usingnamespace_set.putAssumeCapacity(new_decl_index, declaration.flags.is_pub); | | |
| 4244 | } | | |
| 4245 | new_decl.src_line = line; | 4271 | new_decl.src_line = line; |
| 4246 | gop.key_ptr.* = new_decl_index; | | |
| 4247 | // Exported decls, comptime decls, usingnamespace decls, and | | |
| 4248 | // test decls if in test mode, get analyzed. | | |
| 4249 | const decl_mod = namespace.file_scope.mod; | | |
| 4250 | const want_analysis = declaration.flags.is_export or switch (kind) { | | |
| 4251 | .anon => unreachable, | | |
| 4252 | .@"comptime", .@"usingnamespace" => true, | | |
| 4253 | .named => false, | | |
| 4254 | .@"test" => a: { | | |
| 4255 | if (!comp.config.is_test) break :a false; | | |
| 4256 | if (decl_mod != zcu.main_mod) break :a false; | | |
| 4257 | if (is_named_test and comp.test_filters.len > 0) { | | |
| 4258 | const decl_fqn = ip.stringToSlice(try namespace.fullyQualifiedName(zcu, decl_name)); | | |
| 4259 | for (comp.test_filters) |test_filter| { | | |
| 4260 | if (mem.indexOf(u8, decl_fqn, test_filter)) |_| break; | | |
| 4261 | } else break :a false; | | |
| 4262 | } | | |
| 4263 | try zcu.test_functions.put(gpa, new_decl_index, {}); | | |
| 4264 | break :a true; | | |
| 4265 | }, | | |
| 4266 | }; | | |
| 4267 | if (want_analysis) { | | |
| 4268 | log.debug("scanDecl queue analyze_decl file='{s}' decl_name='{s}' decl_index={d}", .{ | | |
| 4269 | namespace.file_scope.sub_file_path, ip.stringToSlice(decl_name), new_decl_index, | | |
| 4270 | }); | | |
| 4271 | comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl_index }); | | |
| 4272 | } | | |
| 4273 | new_decl.is_pub = declaration.flags.is_pub; | 4272 | new_decl.is_pub = declaration.flags.is_pub; |
| 4274 | new_decl.is_exported = declaration.flags.is_export; | 4273 | new_decl.is_exported = declaration.flags.is_export; |
| 4275 | new_decl.zir_decl_index = tracked_inst.toOptional(); | 4274 | new_decl.zir_decl_index = tracked_inst.toOptional(); |
| 4276 | new_decl.alive = true; // This Decl corresponds to an AST node and therefore always alive. | 4275 | new_decl.alive = true; // This Decl corresponds to an AST node and is therefore always alive. |
| 4277 | return; | 4276 | break :decl_index .{ false, new_decl_index }; |
| 4278 | } | 4277 | }; |
| 4279 | const decl_index = gop.key_ptr.*; | 4278 | |
| 4280 | const decl = zcu.declPtr(decl_index); | 4279 | const decl = zcu.declPtr(decl_index); |
| 4281 | // Update the AST node of the decl; even if its contents are unchanged, it may | 4280 | |
| 4282 | // have been re-ordered. | 4281 | namespace.decls.putAssumeCapacityNoClobberContext(decl_index, {}, .{ .zcu = zcu }); |
| 4283 | decl.src_node = decl_node; | 4282 | |
| 4284 | decl.src_line = line; | 4283 | const comp = zcu.comp; |
| 4285 | | 4284 | const decl_mod = namespace.file_scope.mod; |
| 4286 | decl.is_pub = declaration.flags.is_pub; | 4285 | const want_analysis = declaration.flags.is_export or switch (kind) { |
| 4287 | decl.is_exported = declaration.flags.is_export; | 4286 | .anon => unreachable, |
| 4288 | decl.kind = kind; | 4287 | .@"comptime" => true, |
| 4289 | decl.zir_decl_index = tracked_inst.toOptional(); | 4288 | .@"usingnamespace" => a: { |
| | 4289 | namespace.usingnamespace_set.putNoClobber(decl_index, declaration.flags.is_pub); |
| | 4290 | break :a true; |
| | 4291 | }, |
| | 4292 | .named => false, |
| | 4293 | .@"test" => a: { |
| | 4294 | if (!comp.config.is_test) break :a false; |
| | 4295 | if (decl_mod != zcu.main_mod) break :a false; |
| | 4296 | if (is_named_test and comp.test_filters.len > 0) { |
| | 4297 | const decl_fqn = ip.stringToSlice(try namespace.fullyQualifiedName(zcu, decl_name)); |
| | 4298 | for (comp.test_filters) |test_filter| { |
| | 4299 | if (mem.indexOf(u8, decl_fqn, test_filter)) |_| break; |
| | 4300 | } else break :a false; |
| | 4301 | } |
| | 4302 | zcu.test_functions.putAssumeCapacity(decl_index, {}); // may clobber on incremental update |
| | 4303 | break :a true; |
| | 4304 | }, |
| | 4305 | }; |
| | 4306 | |
| | 4307 | if (want_analysis) { |
| | 4308 | // We will not queue analysis if the decl has been analyzed on a previous update and |
| | 4309 | // `is_export` is unchanged. In this case, the incremental update mechanism will handle |
| | 4310 | // re-analysis for us if necessary. |
| | 4311 | if (prev_exported != declaration.flags.is_export or decl.analysis == .unreferenced) { |
| | 4312 | log.debug("scanDecl queue analyze_decl file='{s}' decl_name='{s}' decl_index={d}", .{ |
| | 4313 | namespace.file_scope.sub_file_path, ip.stringToSlice(decl_name), decl_index, |
| | 4314 | }); |
| | 4315 | comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = decl_index }); |
| | 4316 | } |
| | 4317 | } |
| | 4318 | |
| 4290 | if (decl.getOwnedFunction(zcu) != null) { | 4319 | if (decl.getOwnedFunction(zcu) != null) { |
| | 4320 | // TODO this logic is insufficient; namespaces we don't re-scan may still require |
| | 4321 | // updated line numbers. Look into this! |
| 4291 | // TODO Look into detecting when this would be unnecessary by storing enough state | 4322 | // TODO Look into detecting when this would be unnecessary by storing enough state |
| 4292 | // in `Decl` to notice that the line number did not change. | 4323 | // in `Decl` to notice that the line number did not change. |
| 4293 | comp.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl_index }); | 4324 | comp.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl_index }); |