1use std::path::PathBuf;
2use std::sync::atomic::{AtomicU32, Ordering};
3
4use ecow::EcoString;
5use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
6
7use syntax::ast::{EnumVariant, Expression, Literal, StructFieldDefinition};
8use syntax::program::{
9 Definition, DefinitionBody, File, Interface, MethodSignatures, Module, ModuleId,
10};
11use syntax::types::{SimpleKind, SubstitutionMap, Symbol, Type, substitute};
12
13pub const ENTRY_MODULE_ID: &str = "_entry_";
14pub const ENTRY_FILE_ID: u32 = 0;
15
16#[derive(Debug, Clone)]
17pub struct ClosedMember {
18 pub display_name: EcoString,
20 pub literal: Literal,
22 pub value: DomainValue,
24}
25
26#[derive(Debug, Clone)]
28pub struct ClosedDomain {
29 pub base: SimpleKind,
30 pub type_display: EcoString,
31 pub members: Vec<ClosedMember>,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
37pub enum DomainValue {
38 Int(i128),
39 Str(String),
40}
41
42impl DomainValue {
43 pub fn from_literal(literal: &Literal, base: SimpleKind) -> Option<DomainValue> {
44 match base {
48 SimpleKind::Rune => match literal {
49 Literal::Char(text) => char_codepoint(text).map(|cp| DomainValue::Int(cp as i128)),
50 Literal::Integer { value, .. } => Some(DomainValue::Int(*value as i64 as i128)),
51 _ => None,
52 },
53 SimpleKind::String => match literal {
54 Literal::String { value, .. } => Some(DomainValue::Str(value.clone())),
55 _ => None,
56 },
57 _ if is_unsigned_base(base) => match literal {
58 Literal::Integer { value, .. } => Some(DomainValue::Int(*value as i128)),
59 _ => None,
60 },
61 _ if base.is_signed_int() => match literal {
62 Literal::Integer { value, .. } => Some(DomainValue::Int(*value as i64 as i128)),
63 _ => None,
64 },
65 _ => None,
66 }
67 }
68}
69
70pub fn is_unsigned_base(base: SimpleKind) -> bool {
73 base.is_unsigned_int() || base == SimpleKind::Uintptr
74}
75
76fn char_codepoint(text: &str) -> Option<u64> {
79 let Some(rest) = text.strip_prefix('\\') else {
80 return text.chars().next().map(|c| c as u64);
81 };
82 match rest.as_bytes().first()? {
83 b'a' => Some(7),
84 b'b' => Some(8),
85 b'f' => Some(12),
86 b'n' => Some(10),
87 b'r' => Some(13),
88 b't' => Some(9),
89 b'v' => Some(11),
90 b'\\' => Some(92),
91 b'\'' => Some(39),
92 b'x' => u64::from_str_radix(&rest[1..], 16).ok(),
93 b'0'..=b'7' => u64::from_str_radix(rest, 8).ok(),
94 _ => None,
95 }
96}
97
98pub struct Store {
99 pub modules: HashMap<String, Module>,
100 pub module_ids: Vec<ModuleId>,
101 pub files: HashMap<u32, String>,
103 pub go_package_names: HashMap<String, String>,
106 pub typedef_paths: HashMap<u32, PathBuf>,
109 visited_modules: HashSet<String>,
110 next_file_id: AtomicU32,
112 pub closed_domains: HashMap<Symbol, ClosedDomain>,
115}
116
117impl Default for Store {
118 fn default() -> Self {
119 Self::new()
120 }
121}
122
123impl Store {
124 pub fn new() -> Self {
125 let prelude_module = Module::new("prelude");
126 let nominal_module = Module::nominal();
127
128 let modules = vec![
129 (prelude_module.id.clone(), prelude_module),
130 (nominal_module.id.clone(), nominal_module),
131 ]
132 .into_iter()
133 .collect();
134
135 let module_ids = vec!["prelude".to_string()];
136
137 Self {
138 files: Default::default(),
139 modules,
140 module_ids,
141 go_package_names: Default::default(),
142 typedef_paths: Default::default(),
143 visited_modules: Default::default(),
144 next_file_id: AtomicU32::new(2), closed_domains: Default::default(),
146 }
147 }
148
149 pub fn new_file_id(&self) -> u32 {
150 self.next_file_id.fetch_add(1, Ordering::Relaxed)
151 }
152
153 pub fn register_file(&mut self, file_id: u32, module_id: &str) {
154 self.files.insert(file_id, module_id.to_string());
155 }
156
157 pub fn entry_module_id(&self) -> &'static str {
158 ENTRY_MODULE_ID
159 }
160
161 pub fn init_entry_module(&mut self) {
163 self.add_module(ENTRY_MODULE_ID);
164 self.register_file(ENTRY_FILE_ID, ENTRY_MODULE_ID);
165 }
166
167 pub fn store_entry_file(
168 &mut self,
169 filename: &str,
170 display_path: &str,
171 source: &str,
172 ast: Vec<Expression>,
173 ) {
174 self.store_file(
175 ENTRY_MODULE_ID,
176 File {
177 id: ENTRY_FILE_ID,
178 module_id: ENTRY_MODULE_ID.to_string(),
179 name: filename.to_string(),
180 display_path: display_path.to_string(),
181 source: source.to_string(),
182 items: ast,
183 },
184 );
185 }
186
187 pub fn store_module(&mut self, module_id: &str, files: Vec<File>) {
188 self.mark_visited(module_id);
189 self.add_module(module_id);
190
191 for file in files {
192 self.store_file(module_id, file);
193 }
194 }
195
196 pub fn store_file(&mut self, module_id: &str, file: File) {
199 self.files.insert(file.id, module_id.to_string());
200
201 let module = self
202 .get_module_mut(module_id)
203 .expect("module must exist to store file");
204
205 if file.is_d_lis() {
206 module.typedefs.insert(file.id, file);
207 } else {
208 module.files.insert(file.id, file);
209 }
210 }
211
212 pub fn get_file(&self, file_id: u32) -> Option<&File> {
213 let module_id = self.files.get(&file_id)?;
214 let module = self.get_module(module_id)?;
215 module
216 .get_file(file_id)
217 .or_else(|| module.get_typedef_by_id(file_id))
218 }
219
220 pub fn get_file_mut(&mut self, file_id: u32) -> Option<&mut File> {
221 let module_id = self.files.get(&file_id)?.clone();
222 let module = self.modules.get_mut(&module_id)?;
223 module
224 .files
225 .get_mut(&file_id)
226 .or_else(|| module.typedefs.get_mut(&file_id))
227 }
228
229 pub fn get_module(&self, module_id: &str) -> Option<&Module> {
230 self.modules.get(module_id)
231 }
232
233 pub fn has(&self, module_id: &str) -> bool {
234 self.modules.contains_key(module_id)
235 }
236
237 pub fn add_module(&mut self, module_id: &str) {
238 if self.modules.contains_key(module_id) {
239 return;
240 }
241
242 self.modules
243 .insert(module_id.to_string(), Module::new(module_id));
244 self.module_ids.push(module_id.to_string());
245 }
246
247 pub fn get_module_mut(&mut self, module_id: &str) -> Option<&mut Module> {
248 self.modules.get_mut(module_id)
249 }
250
251 pub fn is_visited(&self, module_id: &str) -> bool {
252 self.visited_modules.contains(module_id)
253 }
254
255 pub fn mark_visited(&mut self, module_id: &str) {
256 self.visited_modules.insert(module_id.to_string());
257 }
258
259 pub fn get_definition(&self, qualified_name: &str) -> Option<&Definition> {
260 let module_name = self.module_for_qualified_name(qualified_name)?;
261
262 self.get_module(module_name)?
263 .definitions
264 .get(qualified_name)
265 }
266
267 pub fn module_for_qualified_name<'a>(&'a self, qualified_name: &'a str) -> Option<&'a str> {
268 syntax::types::module_for_qualified_name(
269 qualified_name,
270 self.modules.keys().map(String::as_str),
271 )
272 }
273
274 pub fn variants_of(&self, qualified_name: &str) -> Option<&[EnumVariant]> {
275 match &self.get_definition(qualified_name)?.body {
276 DefinitionBody::Enum { variants, .. } => Some(variants),
277 _ => None,
278 }
279 }
280
281 pub fn variant_of(&self, enum_qualified: &str, variant_name: &str) -> Option<&EnumVariant> {
282 self.variants_of(enum_qualified)?
283 .iter()
284 .find(|v| v.name == variant_name)
285 }
286
287 pub fn is_nominal_defined_type(&self, qualified_name: &str) -> bool {
288 match self.get_definition(qualified_name) {
289 Some(def) => def.is_newtype(),
290 None => false,
291 }
292 }
293
294 pub fn build_closed_domains(&mut self) {
295 let mut bases: HashMap<Symbol, (SimpleKind, String)> = HashMap::default();
297 for module in self.modules.values() {
298 for (qualified_name, definition) in &module.definitions {
299 if definition.is_closed_domain()
302 && let Some(base) = definition.ty().underlying_simple_kind()
303 && !base.is_float()
304 {
305 bases.insert(qualified_name.clone(), (base, module.id.clone()));
306 }
307 }
308 }
309
310 if bases.is_empty() {
311 return;
312 }
313
314 let mut members: HashMap<Symbol, Vec<ClosedMember>> = HashMap::default();
315 for module in self.modules.values() {
316 for (qualified_name, definition) in &module.definitions {
317 let Some(const_literal) = definition.const_value() else {
318 continue;
319 };
320 let Type::Nominal { id, .. } = definition.ty() else {
321 continue;
322 };
323 let Some((base, declaring_module)) = bases.get(id) else {
324 continue;
325 };
326 if module.id != *declaring_module {
329 continue;
330 }
331 let Some(value) = DomainValue::from_literal(const_literal, *base) else {
332 continue;
333 };
334 members.entry(id.clone()).or_default().push(ClosedMember {
335 display_name: domain_display_name(qualified_name.as_str()).into(),
336 literal: const_literal.clone(),
337 value,
338 });
339 }
340 }
341
342 let mut domains: HashMap<Symbol, ClosedDomain> = HashMap::default();
343 for (type_id, (base, _)) in bases {
344 let Some(mut domain_members) = members.remove(&type_id) else {
345 continue;
346 };
347 domain_members.sort_by(|a, b| a.value.cmp(&b.value));
348 domains.insert(
349 type_id.clone(),
350 ClosedDomain {
351 base,
352 type_display: domain_display_name(type_id.as_str()).into(),
353 members: domain_members,
354 },
355 );
356 }
357
358 self.closed_domains = domains;
359 }
360
361 pub fn fields_of(&self, qualified_name: &str) -> Option<&[StructFieldDefinition]> {
362 match &self.get_definition(qualified_name)?.body {
363 DefinitionBody::Struct { fields, .. } => Some(fields),
364 _ => None,
365 }
366 }
367
368 pub fn struct_kind(&self, qualified_name: &str) -> Option<syntax::ast::StructKind> {
369 match &self.get_definition(qualified_name)?.body {
370 DefinitionBody::Struct { kind, .. } => Some(*kind),
371 _ => None,
372 }
373 }
374
375 pub fn struct_constructor(&self, qualified_name: &str) -> Option<&Type> {
376 match &self.get_definition(qualified_name)?.body {
377 DefinitionBody::Struct { constructor, .. } => constructor.as_ref(),
378 _ => None,
379 }
380 }
381
382 pub fn parent_interfaces_of(&self, qualified_name: &str) -> Option<&[Type]> {
383 match &self.get_definition(qualified_name)?.body {
384 DefinitionBody::Interface { definition, .. } => Some(&definition.parents),
385 _ => None,
386 }
387 }
388
389 pub fn get_type(&self, qualified_name: &str) -> Option<&Type> {
390 self.get_definition(qualified_name)
391 .map(|definition| definition.ty())
392 }
393
394 pub fn get_interface(&self, qualified_name: &str) -> Option<&Interface> {
395 match &self.get_definition(qualified_name)?.body {
396 DefinitionBody::Interface { definition, .. } => Some(definition),
397 _ => None,
398 }
399 }
400
401 pub fn is_interface(&self, ty: &Type) -> bool {
402 matches!(ty, Type::Nominal { id, .. } if self.get_interface(id.as_str()).is_some())
403 }
404
405 pub fn is_nilable_go_type(&self, ty: &Type) -> bool {
406 if ty.is_ref() || matches!(ty, Type::Function(_)) {
407 return true;
408 }
409 let Type::Nominal { id, .. } = ty else {
410 return false;
411 };
412 if self.get_definition(id.as_str()).is_none() {
413 return false;
414 }
415 if self.get_interface(id.as_str()).is_some() {
416 return true;
417 }
418 match ty.get_underlying() {
419 Some(Type::Function(_)) => true,
420 Some(u) if u.is_ref() => true,
421 _ => false,
422 }
423 }
424
425 pub fn peel_alias(&self, ty: &Type) -> Type {
426 syntax::types::peel_alias(ty, |id| {
427 self.get_definition(id)
428 .is_some_and(Definition::is_type_alias)
429 })
430 }
431
432 pub fn deep_resolve_alias(&self, ty: &Type) -> Type {
433 let mut current = ty.clone();
434 let mut seen: HashSet<Symbol> = HashSet::default();
435 loop {
436 let Type::Nominal { id, params, .. } = ¤t else {
437 return current;
438 };
439 if !seen.insert(id.clone()) {
440 return current;
441 }
442 let Some(def) = self.get_definition(id.as_str()) else {
443 return current;
444 };
445 if !matches!(def.body, DefinitionBody::TypeAlias { .. }) {
446 return current;
447 }
448 let def_ty = &def.ty;
449 let (vars, body) = match def_ty {
450 Type::Forall { vars, body } => (vars.clone(), body.as_ref().clone()),
451 other => (vec![], other.clone()),
452 };
453 let map: SubstitutionMap = vars.iter().cloned().zip(params.iter().cloned()).collect();
454 current = substitute(&body, &map);
455 }
456 }
457
458 pub fn peel_alias_deep(&self, ty: &Type) -> Type {
459 match self.peel_alias(ty) {
460 Type::Compound { kind, args } => Type::Compound {
461 kind,
462 args: args.iter().map(|a| self.peel_alias_deep(a)).collect(),
463 },
464 Type::Tuple(elements) => {
465 Type::Tuple(elements.iter().map(|e| self.peel_alias_deep(e)).collect())
466 }
467 Type::Nominal {
468 id,
469 params,
470 underlying_ty,
471 } => Type::Nominal {
472 id,
473 params: params.iter().map(|p| self.peel_alias_deep(p)).collect(),
474 underlying_ty,
475 },
476 Type::Function(f) => {
477 let f = std::sync::Arc::try_unwrap(f).unwrap_or_else(|arc| (*arc).clone());
478 Type::function(
479 f.params.iter().map(|p| self.peel_alias_deep(p)).collect(),
480 f.param_mutability,
481 f.bounds,
482 Box::new(self.peel_alias_deep(&f.return_type)),
483 )
484 }
485 other => other,
486 }
487 }
488
489 pub fn get_own_methods(&self, qualified_name: &str) -> Option<&MethodSignatures> {
490 match &self.get_definition(qualified_name)?.body {
491 DefinitionBody::Struct { methods, .. } => Some(methods),
492 DefinitionBody::TypeAlias { methods, .. } => Some(methods),
493 DefinitionBody::Enum { methods, .. } => Some(methods),
494 _ => None,
495 }
496 }
497
498 pub fn get_all_methods(
499 &self,
500 ty: &Type,
501 trait_bounds: &HashMap<Symbol, Vec<Type>>,
502 ) -> MethodSignatures {
503 let mut visited = HashSet::default();
504 self.get_all_methods_recursive(ty, trait_bounds, &mut visited)
505 }
506
507 fn get_all_methods_recursive(
508 &self,
509 ty: &Type,
510 trait_bounds: &HashMap<Symbol, Vec<Type>>,
511 visited: &mut HashSet<String>,
512 ) -> MethodSignatures {
513 let stripped = ty.strip_refs();
514 let Some(qualified_name) = method_lookup_key(&stripped) else {
515 return MethodSignatures::default();
516 };
517
518 if !visited.insert(qualified_name.as_str().to_string()) {
520 return MethodSignatures::default();
521 }
522
523 if let Some(interface) = self.get_interface(&qualified_name) {
524 let mut all_interface_methods = MethodSignatures::default();
525
526 let type_args = ty.get_type_params().unwrap_or_default();
527 let map: SubstitutionMap = interface
528 .generics
529 .iter()
530 .map(|g| g.name.clone())
531 .zip(type_args.iter().cloned())
532 .collect();
533
534 for (name, method_ty) in &interface.methods {
535 let substituted = substitute(method_ty, &map);
536 all_interface_methods.insert(name.clone(), substituted.with_receiver_placeholder());
537 }
538
539 for parent in &interface.parents {
540 for (name, method_ty) in
541 self.get_all_methods_recursive(parent, trait_bounds, visited)
542 {
543 all_interface_methods.insert(name, method_ty);
544 }
545 }
546
547 return all_interface_methods;
548 }
549
550 if let Some(bound_types) = trait_bounds.get(&qualified_name) {
551 return bound_types
552 .iter()
553 .flat_map(|interface_ty| {
554 self.get_all_methods_recursive(interface_ty, trait_bounds, visited)
555 })
556 .collect();
557 }
558
559 let mut methods = self
560 .get_own_methods(&qualified_name)
561 .cloned()
562 .unwrap_or_default();
563
564 if let Some(definition) = self.get_definition(&qualified_name)
566 && matches!(definition.body, DefinitionBody::TypeAlias { .. })
567 {
568 let alias_ty = &definition.ty;
569 let underlying = match alias_ty {
570 Type::Forall { body, .. } => body.as_ref(),
571 other => other,
572 };
573 let underlying_key = match underlying {
574 Type::Nominal { id, .. } => Some(id.as_str().to_string()),
575 Type::Simple(kind) => Some(format!("prelude.{}", kind.leaf_name())),
576 Type::Compound { kind, .. } => Some(format!("prelude.{}", kind.leaf_name())),
577 _ => None,
578 };
579 if let Some(k) = underlying_key
583 && k != qualified_name.as_str()
584 {
585 let alias_ty = alias_ty.clone();
586 for (name, method_ty) in
587 self.get_all_methods_recursive(&alias_ty, trait_bounds, visited)
588 {
589 methods.entry(name).or_insert(method_ty);
590 }
591 }
592 }
593
594 methods
595 }
596
597 pub fn get_methods_from_bounds(
598 &self,
599 qualified_name: &str,
600 trait_bounds: &HashMap<Symbol, Vec<Type>>,
601 ) -> MethodSignatures {
602 if let Some(bound_types) = trait_bounds.get(qualified_name) {
603 return bound_types
604 .iter()
605 .flat_map(|interface_ty| self.get_all_methods(interface_ty, trait_bounds))
606 .collect();
607 }
608 MethodSignatures::default()
609 }
610}
611
612fn domain_display_name(qualified: &str) -> String {
613 let Some((module, name)) = qualified.rsplit_once('.') else {
614 return qualified.to_string();
615 };
616 match module.strip_prefix("go:") {
617 Some(go_module) => {
618 let package = go_module.rsplit('/').next().unwrap_or(go_module);
619 format!("{package}.{name}")
620 }
621 None => name.to_string(),
622 }
623}
624
625fn method_lookup_key(ty: &Type) -> Option<Symbol> {
629 match ty {
630 Type::Nominal { id, .. } => Some(id.clone()),
631 Type::Compound { kind, .. } => Some(Symbol::from_parts("prelude", kind.leaf_name())),
632 Type::Simple(kind) => Some(Symbol::from_parts("prelude", kind.leaf_name())),
633 _ => None,
634 }
635}
636
637#[cfg(test)]
638mod closed_domain_tests {
639 use super::*;
640 use syntax::ast::StructKind;
641 use syntax::program::{Attributes, TypeAttribute, Visibility};
642
643 fn nominal_int(id: &str) -> Type {
644 Type::Nominal {
645 id: Symbol::from_raw(id),
646 params: vec![],
647 underlying_ty: Some(Box::new(Type::Simple(SimpleKind::Int))),
648 }
649 }
650
651 fn struct_def(ty: Type, closed_domain: bool) -> Definition {
652 let mut attributes = Attributes::default();
653 if closed_domain {
654 attributes.insert(TypeAttribute::ClosedDomain, ());
655 }
656 Definition {
657 visibility: Visibility::Public,
658 ty,
659 name: None,
660 name_span: None,
661 doc: None,
662 body: DefinitionBody::Struct {
663 generics: vec![],
664 fields: vec![],
665 kind: StructKind::Tuple,
666 methods: Default::default(),
667 constructor: None,
668 attributes,
669 },
670 }
671 }
672
673 fn int_const(ty: Type, value: u64) -> Definition {
674 Definition {
675 visibility: Visibility::Public,
676 ty,
677 name: None,
678 name_span: None,
679 doc: None,
680 body: DefinitionBody::Value {
681 allowed_lints: vec![],
682 go_hints: vec![],
683 go_name: None,
684 const_value: Some(Literal::Integer { value, text: None }),
685 },
686 }
687 }
688
689 fn insert(store: &mut Store, module: &str, name: &str, def: Definition) {
690 store.add_module(module);
691 store
692 .get_module_mut(module)
693 .unwrap()
694 .definitions
695 .insert(Symbol::from_raw(name), def);
696 }
697
698 #[test]
699 fn tagged_type_with_members_is_indexed_and_sorted() {
700 let mut store = Store::new();
701 let ty = nominal_int("m.Weekday");
702 insert(&mut store, "m", "m.Weekday", struct_def(ty.clone(), true));
703 insert(&mut store, "m", "m.Saturday", int_const(ty.clone(), 6));
704 insert(&mut store, "m", "m.Sunday", int_const(ty.clone(), 0));
705
706 store.build_closed_domains();
707
708 let domain = store
709 .closed_domains
710 .get("m.Weekday")
711 .expect("tagged type with members should be indexed");
712 assert_eq!(domain.base, SimpleKind::Int);
713 assert_eq!(domain.type_display.as_str(), "Weekday");
714 let names: Vec<&str> = domain
715 .members
716 .iter()
717 .map(|m| m.display_name.as_str())
718 .collect();
719 assert_eq!(names, vec!["Sunday", "Saturday"]);
720 }
721
722 #[test]
723 fn untagged_type_is_absent() {
724 let mut store = Store::new();
725 let ty = nominal_int("m.Plain");
726 insert(&mut store, "m", "m.Plain", struct_def(ty.clone(), false));
727 insert(&mut store, "m", "m.One", int_const(ty, 1));
728
729 store.build_closed_domains();
730
731 assert!(store.closed_domains.is_empty());
732 }
733
734 #[test]
735 fn tagged_type_without_members_records_no_domain() {
736 let mut store = Store::new();
737 insert(
738 &mut store,
739 "m",
740 "m.Empty",
741 struct_def(nominal_int("m.Empty"), true),
742 );
743
744 store.build_closed_domains();
745
746 assert!(!store.closed_domains.contains_key("m.Empty"));
747 }
748
749 #[test]
750 fn const_in_other_module_does_not_widen_domain() {
751 let mut store = Store::new();
752 let ty = nominal_int("lib.Weekday");
753 insert(
754 &mut store,
755 "lib",
756 "lib.Weekday",
757 struct_def(ty.clone(), true),
758 );
759 insert(&mut store, "lib", "lib.Sunday", int_const(ty.clone(), 0));
760 insert(&mut store, "user", "user.Bad", int_const(ty, 99));
761
762 store.build_closed_domains();
763
764 let domain = store.closed_domains.get("lib.Weekday").unwrap();
765 let names: Vec<&str> = domain
766 .members
767 .iter()
768 .map(|m| m.display_name.as_str())
769 .collect();
770 assert_eq!(names, vec!["Sunday"]);
771 }
772}