| ... | ... | @@ -4076,17 +4076,34 @@ pub fn scanNamespace( |
| 4076 | 4076 | const gpa = zcu.gpa; |
| 4077 | 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 | 4091 | var seen_decls: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{}; |
| 4080 | 4092 | defer seen_decls.deinit(gpa); |
| 4081 | 4093 | |
| 4082 | 4094 | try zcu.comp.work_queue.ensureUnusedCapacity(decls.len); |
| 4095 | |
| 4096 | namespace.decls.clearRetainingCapacity(); |
| 4083 | 4097 | try namespace.decls.ensureTotalCapacity(gpa, decls.len); |
| 4084 | 4098 | |
| 4099 | namespace.usingnamespace_set.clearRetainingCapacity(); |
| 4100 | |
| 4085 | 4101 | var scan_decl_iter: ScanDeclIter = .{ |
| 4086 | 4102 | .zcu = zcu, |
| 4087 | 4103 | .namespace_index = namespace_index, |
| 4088 | 4104 | .parent_decl = parent_decl, |
| 4089 | 4105 | .seen_decls = &seen_decls, |
| 4106 | .existing_by_inst = &existing_by_inst, |
| 4090 | 4107 | .pass = .named, |
| 4091 | 4108 | }; |
| 4092 | 4109 | for (decls) |decl_inst| { |
| ... | ... | @@ -4118,6 +4135,7 @@ const ScanDeclIter = struct { |
| 4118 | 4135 | namespace_index: Namespace.Index, |
| 4119 | 4136 | parent_decl: *Decl, |
| 4120 | 4137 | seen_decls: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void), |
| 4138 | existing_by_inst: *const std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, Decl.Index), |
| 4121 | 4139 | /// Decl scanning is run in two passes, so that we can detect when a generated |
| 4122 | 4140 | /// name would clash with an explicit name and use a different one. |
| 4123 | 4141 | pass: enum { named, unnamed }, |
| ... | ... | @@ -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 | 4249 | const tracked_inst = try ip.trackZir(gpa, iter.parent_decl.getFileScope(zcu), decl_inst); |
| 4228 | 4250 | |
| 4229 | 4251 | // We create a Decl for it regardless of analysis status. |
| 4230 | | const gop = try namespace.decls.getOrPutContextAdapted( |
| 4231 | | gpa, |
| 4232 | | decl_name, |
| 4233 | | DeclAdapter{ .zcu = zcu }, |
| 4234 | | Namespace.DeclContext{ .zcu = zcu }, |
| 4235 | | ); |
| 4236 | | const comp = zcu.comp; |
| 4237 | | if (!gop.found_existing) { |
| 4252 | |
| 4253 | const prev_exported, const decl_index = if (iter.existing_by_inst.get(tracked_inst)) |decl_index| decl_index: { |
| 4254 | // We need only update this existing Decl. |
| 4255 | const decl = zcu.declPtr(decl_index); |
| 4256 | const was_exported = decl.is_exported; |
| 4257 | assert(decl.kind == kind); // ZIR tracking should preserve this |
| 4258 | assert(decl.alive); |
| 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 | 4267 | const new_decl_index = try zcu.allocateNewDecl(namespace_index, decl_node); |
| 4239 | 4268 | const new_decl = zcu.declPtr(new_decl_index); |
| 4240 | 4269 | new_decl.kind = kind; |
| 4241 | 4270 | new_decl.name = decl_name; |
| 4242 | | if (kind == .@"usingnamespace") { |
| 4243 | | namespace.usingnamespace_set.putAssumeCapacity(new_decl_index, declaration.flags.is_pub); |
| 4244 | | } |
| 4245 | 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 | 4272 | new_decl.is_pub = declaration.flags.is_pub; |
| 4274 | 4273 | new_decl.is_exported = declaration.flags.is_export; |
| 4275 | 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. |
| 4277 | | return; |
| 4278 | | } |
| 4279 | | const decl_index = gop.key_ptr.*; |
| 4275 | new_decl.alive = true; // This Decl corresponds to an AST node and is therefore always alive. |
| 4276 | break :decl_index .{ false, new_decl_index }; |
| 4277 | }; |
| 4278 | |
| 4280 | 4279 | const decl = zcu.declPtr(decl_index); |
| 4281 | | // Update the AST node of the decl; even if its contents are unchanged, it may |
| 4282 | | // have been re-ordered. |
| 4283 | | decl.src_node = decl_node; |
| 4284 | | decl.src_line = line; |
| 4285 | | |
| 4286 | | decl.is_pub = declaration.flags.is_pub; |
| 4287 | | decl.is_exported = declaration.flags.is_export; |
| 4288 | | decl.kind = kind; |
| 4289 | | decl.zir_decl_index = tracked_inst.toOptional(); |
| 4280 | |
| 4281 | namespace.decls.putAssumeCapacityNoClobberContext(decl_index, {}, .{ .zcu = zcu }); |
| 4282 | |
| 4283 | const comp = zcu.comp; |
| 4284 | const decl_mod = namespace.file_scope.mod; |
| 4285 | const want_analysis = declaration.flags.is_export or switch (kind) { |
| 4286 | .anon => unreachable, |
| 4287 | .@"comptime" => true, |
| 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 | 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 | 4322 | // TODO Look into detecting when this would be unnecessary by storing enough state |
| 4292 | 4323 | // in `Decl` to notice that the line number did not change. |
| 4293 | 4324 | comp.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl_index }); |