Fork of daniellemaywood.uk/gleam — Wasm codegen work
34 kB
1035 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2019 The Gleam contributors
3
4use std::collections::{HashMap, HashSet};
5
6use ecow::EcoString;
7use lsp_types::Location;
8
9use gleam_core::{
10 analyse,
11 ast::{
12 self, ArgNames, AssignName, BitArraySize, ClauseGuard, CustomType, Function,
13 ModuleConstant, Pattern, RecordConstructor, SrcSpan, TypedExpr, TypedModule, visit::Visit,
14 },
15 build::{Located, UnqualifiedImport},
16 reference::RecordLabel,
17 type_::{
18 ModuleInterface, ModuleValueConstructor, Type, ValueConstructor, ValueConstructorVariant,
19 error::{Named, VariableOrigin},
20 },
21};
22
23use super::{
24 compiler::ModuleSourceInformation, rename::RenameTarget, src_span_to_lsp_range, url_from_path,
25};
26
27#[derive(Debug)]
28pub enum Referenced {
29 LocalVariable {
30 definition_location: SrcSpan,
31 location: SrcSpan,
32 origin: Option<VariableOrigin>,
33 name: EcoString,
34 },
35 ModuleName {
36 module_name: EcoString,
37 module_alias: EcoString,
38 location: SrcSpan,
39 },
40 ModuleValue {
41 module: EcoString,
42 name: EcoString,
43 location: SrcSpan,
44 name_kind: Named,
45 target_kind: RenameTarget,
46 },
47 ModuleType {
48 module: EcoString,
49 name: EcoString,
50 location: SrcSpan,
51 target_kind: RenameTarget,
52 },
53 TypeVariable {
54 location: SrcSpan,
55 name: EcoString,
56 },
57 Label {
58 type_module: EcoString,
59 type_name: EcoString,
60 label: EcoString,
61 location: SrcSpan,
62 },
63}
64
65pub fn reference_for_ast_node(
66 found: Located<'_>,
67 current_module: &EcoString,
68) -> Option<Referenced> {
69 match found {
70 Located::Expression {
71 expression:
72 TypedExpr::Var {
73 constructor:
74 ValueConstructor {
75 variant:
76 ValueConstructorVariant::LocalVariable {
77 location: definition_location,
78 origin,
79 },
80 ..
81 },
82 location,
83 name,
84 },
85 ..
86 } => Some(Referenced::LocalVariable {
87 definition_location: *definition_location,
88 location: *location,
89 origin: Some(origin.clone()),
90 name: name.clone(),
91 }),
92 Located::Pattern(Pattern::Variable {
93 location,
94 origin,
95 name,
96 ..
97 }) => Some(Referenced::LocalVariable {
98 definition_location: *location,
99 location: *location,
100 origin: Some(origin.clone()),
101 name: name.clone(),
102 }),
103 Located::Pattern(Pattern::BitArraySize(BitArraySize::Variable {
104 constructor,
105 location,
106 name,
107 ..
108 })) => constructor
109 .as_ref()
110 .and_then(|constructor| match &constructor.variant {
111 ValueConstructorVariant::LocalVariable {
112 location: definition_location,
113 origin,
114 } => Some(Referenced::LocalVariable {
115 definition_location: *definition_location,
116 location: *location,
117 origin: Some(origin.clone()),
118 name: name.clone(),
119 }),
120 ValueConstructorVariant::ModuleConstant { .. }
121 | ValueConstructorVariant::ModuleFn { .. }
122 | ValueConstructorVariant::Record { .. } => None,
123 }),
124 Located::Pattern(Pattern::Assign { location, name, .. }) => {
125 Some(Referenced::LocalVariable {
126 definition_location: *location,
127 location: *location,
128 origin: None,
129 name: name.clone(),
130 })
131 }
132 Located::Arg(arg) => match &arg.names {
133 ArgNames::Named { location, name }
134 | ArgNames::NamedLabelled {
135 name_location: location,
136 name,
137 ..
138 } => Some(Referenced::LocalVariable {
139 definition_location: *location,
140 location: *location,
141 origin: None,
142 name: name.clone(),
143 }),
144 ArgNames::Discard { .. } | ArgNames::LabelledDiscard { .. } => None,
145 },
146 Located::Expression {
147 expression:
148 TypedExpr::Var {
149 constructor:
150 ValueConstructor {
151 variant:
152 ValueConstructorVariant::ModuleConstant { module, name, .. }
153 | ValueConstructorVariant::ModuleFn { module, name, .. },
154 ..
155 },
156 location,
157 ..
158 },
159 ..
160 } => Some(Referenced::ModuleValue {
161 module: module.clone(),
162 name: name.clone(),
163 location: *location,
164 name_kind: Named::Function,
165 target_kind: RenameTarget::Unqualified,
166 }),
167
168 Located::Expression {
169 expression:
170 TypedExpr::ModuleSelect {
171 module_name,
172 label,
173 constructor:
174 ModuleValueConstructor::Fn { .. } | ModuleValueConstructor::Constant { .. },
175 location,
176 field_start,
177 ..
178 },
179 ..
180 } => Some(Referenced::ModuleValue {
181 module: module_name.clone(),
182 name: label.clone(),
183 location: SrcSpan::new(*field_start, location.end),
184 name_kind: Named::Function,
185 target_kind: RenameTarget::Qualified,
186 }),
187
188 Located::ModuleFunction(Function {
189 name: Some((location, name)),
190 ..
191 })
192 | Located::ModuleConstant(ModuleConstant {
193 name,
194 name_location: location,
195 ..
196 }) => Some(Referenced::ModuleValue {
197 module: current_module.clone(),
198 name: name.clone(),
199 location: *location,
200 name_kind: Named::Function,
201 target_kind: RenameTarget::Definition,
202 }),
203 Located::Expression {
204 expression:
205 TypedExpr::Var {
206 constructor:
207 ValueConstructor {
208 variant: ValueConstructorVariant::Record { module, name, .. },
209 ..
210 },
211 location,
212 ..
213 },
214 ..
215 } => Some(Referenced::ModuleValue {
216 module: module.clone(),
217 name: name.clone(),
218 location: *location,
219 name_kind: Named::CustomTypeVariant,
220 target_kind: RenameTarget::Unqualified,
221 }),
222 Located::Expression {
223 expression:
224 TypedExpr::ModuleSelect {
225 module_name,
226 label,
227 constructor: ModuleValueConstructor::Record { .. },
228 location,
229 field_start,
230 ..
231 },
232 ..
233 } => Some(Referenced::ModuleValue {
234 module: module_name.clone(),
235 name: label.clone(),
236 location: SrcSpan::new(*field_start, location.end),
237 name_kind: Named::CustomTypeVariant,
238 target_kind: RenameTarget::Qualified,
239 }),
240 Located::VariantConstructorDefinition(RecordConstructor {
241 name,
242 name_location,
243 ..
244 }) => Some(Referenced::ModuleValue {
245 module: current_module.clone(),
246 name: name.clone(),
247 location: *name_location,
248 name_kind: Named::CustomTypeVariant,
249 target_kind: RenameTarget::Definition,
250 }),
251 Located::Pattern(Pattern::Constructor {
252 constructor: analyse::Inferred::Known(constructor),
253 module: module_select,
254 name_location: location,
255 ..
256 }) => Some(Referenced::ModuleValue {
257 module: constructor.module.clone(),
258 name: constructor.name.clone(),
259 location: *location,
260 name_kind: Named::CustomTypeVariant,
261 target_kind: if module_select.is_some() {
262 RenameTarget::Qualified
263 } else {
264 RenameTarget::Unqualified
265 },
266 }),
267 Located::StringPrefixPatternVariable { location, name, .. } => {
268 Some(Referenced::LocalVariable {
269 definition_location: location,
270 location,
271 origin: None,
272 name: name.clone(),
273 })
274 }
275 Located::Annotation { ast, type_ } => match ast {
276 ast::TypeAst::Constructor(constructor)
277 if let Some((module, name)) = type_.named_type_name() =>
278 {
279 let target_kind = if constructor.name.is_qualified() {
280 RenameTarget::Qualified
281 } else {
282 RenameTarget::Unqualified
283 };
284 let location = constructor.name.name_location()?;
285
286 Some(Referenced::ModuleType {
287 module,
288 name,
289 location,
290 target_kind,
291 })
292 }
293
294 ast::TypeAst::Var(variable) => Some(Referenced::TypeVariable {
295 location: variable.location,
296 name: variable.name.clone(),
297 }),
298
299 ast::TypeAst::Constructor(_)
300 | ast::TypeAst::Fn(_)
301 | ast::TypeAst::Tuple(_)
302 | ast::TypeAst::Hole(_) => None,
303 },
304 Located::TypeVariable { name, location } => {
305 Some(Referenced::TypeVariable { location, name })
306 }
307 Located::ModuleCustomType(CustomType {
308 name,
309 name_location,
310 ..
311 }) => Some(Referenced::ModuleType {
312 module: current_module.clone(),
313 name: name.clone(),
314 location: *name_location,
315 target_kind: RenameTarget::Definition,
316 }),
317 Located::ModuleName {
318 location,
319 module_name,
320 module_alias,
321 ..
322 } => Some(Referenced::ModuleName {
323 module_name,
324 module_alias,
325 location,
326 }),
327 Located::ModuleImport(import) => {
328 let module_name = match &import.as_name {
329 Some((
330 AssignName::Variable(module_alias) | AssignName::Discard(module_alias),
331 alias_location,
332 )) => Referenced::ModuleName {
333 module_name: import.module.clone(),
334 module_alias: module_alias.clone(),
335 location: SrcSpan {
336 start: alias_location.end - (module_alias.len() as u32),
337 end: alias_location.end,
338 },
339 },
340 None => Referenced::ModuleName {
341 module_name: import.module.clone(),
342 module_alias: import
343 .module
344 .split('/')
345 .next_back()
346 .map(EcoString::from)
347 .unwrap_or_else(|| import.module.clone()),
348 location: import.module_location,
349 },
350 };
351
352 Some(module_name)
353 }
354
355 Located::ClauseGuard(ClauseGuard::Var {
356 location,
357 type_: _,
358 name,
359 definition_location,
360 origin,
361 }) => Some(Referenced::LocalVariable {
362 definition_location: *definition_location,
363 location: *location,
364 origin: Some(origin.clone()),
365 name: name.clone(),
366 }),
367
368 Located::ClauseGuard(ClauseGuard::ModuleSelect {
369 location,
370 field_start,
371 label,
372 module_name,
373 ..
374 }) => Some(Referenced::ModuleValue {
375 module: module_name.clone(),
376 name: label.clone(),
377 location: SrcSpan::new(*field_start, location.end),
378 name_kind: Named::Function,
379 target_kind: RenameTarget::Qualified,
380 }),
381
382 Located::UnqualifiedImport(
383 import @ UnqualifiedImport {
384 name,
385 module,
386 is_type,
387 location: _,
388 imported_name_location,
389 },
390 ) => {
391 if is_type {
392 Some(Referenced::ModuleType {
393 module: module.clone(),
394 name: name.clone(),
395 location: *imported_name_location,
396 target_kind: RenameTarget::Unqualified,
397 })
398 } else {
399 Some(Referenced::ModuleValue {
400 module: module.clone(),
401 name: name.clone(),
402 location: *imported_name_location,
403 name_kind: import.name_kind(),
404 target_kind: RenameTarget::Unqualified,
405 })
406 }
407 }
408
409 Located::RecordLabelUsage {
410 record_type,
411 label,
412 location,
413 ..
414 }
415 | Located::RecordAccessLabel {
416 record_type,
417 label,
418 location,
419 ..
420 } => record_type
421 .named_type_name()
422 .map(|(type_module, type_name)| Referenced::Label {
423 type_module,
424 type_name,
425 label: label.clone(),
426 location,
427 }),
428
429 // A label at its definition in a custom type, which lives in the
430 // current module.
431 Located::RecordLabelDefinition {
432 type_name,
433 label,
434 location,
435 ..
436 } => Some(Referenced::Label {
437 type_module: current_module.clone(),
438 type_name,
439 label,
440 location,
441 }),
442
443 Located::Pattern(_)
444 | Located::ClauseGuard(_)
445 | Located::PatternSpread { .. }
446 | Located::Statement(_)
447 | Located::Expression { .. }
448 | Located::FunctionBody(_)
449 | Located::Label { .. }
450 | Located::Constant(_)
451 | Located::ModuleFunction(_)
452 | Located::ModuleTypeAlias(_) => None,
453 }
454}
455
456pub fn find_module_references(
457 module_name: EcoString,
458 name: EcoString,
459 modules: &im::HashMap<EcoString, ModuleInterface>,
460 sources: &HashMap<EcoString, ModuleSourceInformation>,
461 layer: ast::Layer,
462) -> Vec<Location> {
463 let mut reference_locations = Vec::new();
464
465 for module in modules.values() {
466 if module.name == module_name || module.references.imported_modules.contains(&module_name) {
467 let Some(source_information) = sources.get(&module.name) else {
468 continue;
469 };
470
471 find_references_in_module(
472 &module_name,
473 &name,
474 module,
475 source_information,
476 &mut reference_locations,
477 layer,
478 );
479 }
480 }
481
482 reference_locations
483}
484
485pub fn find_module_references_in_module(
486 module_name: EcoString,
487 name: EcoString,
488 module: &ModuleInterface,
489 source_information: &ModuleSourceInformation,
490 layer: ast::Layer,
491) -> Vec<Location> {
492 let mut reference_locations = Vec::new();
493
494 find_references_in_module(
495 &module_name,
496 &name,
497 module,
498 source_information,
499 &mut reference_locations,
500 layer,
501 );
502
503 reference_locations
504}
505
506pub fn find_label_references(
507 type_module: EcoString,
508 type_name: EcoString,
509 label: EcoString,
510 modules: &im::HashMap<EcoString, ModuleInterface>,
511 sources: &HashMap<EcoString, ModuleSourceInformation>,
512) -> Vec<Location> {
513 let mut reference_locations = Vec::new();
514
515 // Unlike values and types, a label can be referenced in a module that
516 // doesn't import the type's defining module: a record value can be
517 // obtained transitively through another module and have its fields
518 // accessed. So every module has to be searched.
519 for module in modules.values() {
520 let Some(source_information) = sources.get(&module.name) else {
521 continue;
522 };
523 reference_locations.extend(find_label_references_in_module(
524 type_module.clone(),
525 type_name.clone(),
526 label.clone(),
527 module,
528 source_information,
529 ));
530 }
531
532 reference_locations
533}
534
535pub fn find_label_references_in_module(
536 type_module: EcoString,
537 type_name: EcoString,
538 label: EcoString,
539 module: &ModuleInterface,
540 source_information: &ModuleSourceInformation,
541) -> Vec<Location> {
542 let mut reference_locations = Vec::new();
543
544 let Some(uri) = url_from_path(source_information.path.as_str()) else {
545 return reference_locations;
546 };
547
548 let key = RecordLabel {
549 type_module,
550 type_name,
551 label,
552 };
553 let definitions = module.references.label_definitions.get(&key);
554 let references = module.references.label_references.get(&key);
555 let locations = definitions
556 .into_iter()
557 .flatten()
558 .map(|definition| definition.location)
559 .chain(
560 references
561 .into_iter()
562 .flatten()
563 .map(|reference| reference.location),
564 );
565
566 for location in locations {
567 reference_locations.push(Location {
568 uri: uri.clone(),
569 range: src_span_to_lsp_range(location, &source_information.line_numbers),
570 });
571 }
572
573 reference_locations
574}
575
576fn find_references_in_module(
577 module_name: &EcoString,
578 name: &EcoString,
579 module: &ModuleInterface,
580 source_information: &ModuleSourceInformation,
581 reference_locations: &mut Vec<Location>,
582 layer: ast::Layer,
583) {
584 let reference_map = match layer {
585 ast::Layer::Value => &module.references.value_references,
586 ast::Layer::Type => &module.references.type_references,
587 };
588
589 let Some(references) = reference_map.get(&(module_name.clone(), name.clone())) else {
590 return;
591 };
592
593 let Some(uri) = url_from_path(source_information.path.as_str()) else {
594 return;
595 };
596
597 for reference in references {
598 reference_locations.push(Location {
599 uri: uri.clone(),
600 range: src_span_to_lsp_range(reference.location, &source_information.line_numbers),
601 });
602 }
603}
604
605#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
606pub struct VariableReference {
607 pub location: SrcSpan,
608 pub kind: VariableReferenceKind,
609}
610
611#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
612pub enum VariableReferenceKind {
613 Variable,
614 LabelShorthand,
615}
616
617/// How to treat variables defined in alternative patterns
618enum AlternativeVariable {
619 Track,
620 Ignore,
621}
622
623pub struct FindVariableReferences {
624 // Due to the structure of some AST nodes (for example, record updates),
625 // when we traverse the AST it is possible to accidentally duplicate references.
626 // To avoid this, we use a `HashSet` instead of a `Vec` here.
627 // See: https://github.com/gleam-lang/gleam/issues/4859 and the linked PR.
628 references: HashSet<VariableReference>,
629 definition_location: DefinitionLocation,
630 alternative_variable: AlternativeVariable,
631 name: EcoString,
632}
633
634/// Where the variable we're finding references for is defined.
635///
636enum DefinitionLocation {
637 /// This is the location where the variable is defined, nothing special is
638 /// going on here. For example:
639 ///
640 /// ```gleam
641 /// let wibble = 1
642 /// // ^^^^^^ Definition location for `wibble`
643 /// wibble + 1
644 /// ^^^^^^
645 /// // `wibble` used here, defined earlier
646 /// ```
647 ///
648 Regular { location: SrcSpan },
649
650 /// When dealing with alternative patterns and aliases we need special care:
651 /// each usage wil always reference the first alternative where a variable
652 /// is defined and not the following ones. For example:
653 ///
654 /// ```gleam
655 /// case wibble {
656 /// [] as var | [_] as var -> var
657 /// // ^^^ ^^^ If we look where `var` thinks it's defined
658 /// // It will say it's defined here!
659 /// }
660 /// ```
661 ///
662 /// This poses a problem if we start the renaming from the second
663 /// alternative pattern:
664 ///
665 /// ```gleam
666 /// case wibble {
667 /// [] as var | [_] as var -> var
668 /// // ^^^ Since `var` uses the first alternative as its
669 /// // definition location, this would not be considered
670 /// // a reference to that same var.
671 /// }
672 /// ```
673 ///
674 /// So we keep track of the location of this definition, but we also need
675 /// to store the location of the first definition in the alternative case
676 /// (that's `first_alternative_location`), so that when we look for
677 /// references we can check against this one that is canonically used by
678 /// expressions in the AST
679 ///
680 Alternative {
681 location: SrcSpan,
682 first_alternative_location: SrcSpan,
683 },
684}
685
686impl FindVariableReferences {
687 pub fn new(variable_definition_location: SrcSpan, variable_name: EcoString) -> Self {
688 Self {
689 references: HashSet::new(),
690 definition_location: DefinitionLocation::Regular {
691 location: variable_definition_location,
692 },
693 alternative_variable: AlternativeVariable::Ignore,
694 name: variable_name,
695 }
696 }
697
698 /// Where the definition for which we're accumulating references is
699 /// originally defined. In case of alternative patterns this will point to
700 /// the first occurrence of that name! Look at the docs for
701 /// `DefinitionLocation` to learn more on why this is needed.
702 ///
703 fn definition_origin_location(&self) -> SrcSpan {
704 match self.definition_location {
705 DefinitionLocation::Regular { location }
706 | DefinitionLocation::Alternative {
707 first_alternative_location: location,
708 ..
709 } => location,
710 }
711 }
712
713 /// This is the location of the definition for which we're accumulating
714 /// references. In most cases you'll want to use `definition_origin_location`.
715 /// The difference between the two is explained in greater detail in the docs
716 /// for `DefinitionLocation`.
717 ///
718 fn definition_location(&self) -> SrcSpan {
719 match self.definition_location {
720 DefinitionLocation::Regular { location }
721 | DefinitionLocation::Alternative { location, .. } => location,
722 }
723 }
724
725 fn update_alternative_origin(&mut self, alternative_location: SrcSpan) {
726 match self.definition_location {
727 // We've found the location of the origin of an alternative pattern.
728 DefinitionLocation::Regular { location } if alternative_location < location => {
729 self.definition_location = DefinitionLocation::Alternative {
730 location,
731 first_alternative_location: alternative_location,
732 };
733 }
734
735 // Since the new alternative location we've found is smaller, that
736 // is the actual first one for the alternative pattern!
737 DefinitionLocation::Alternative {
738 location,
739 first_alternative_location,
740 } if alternative_location < first_alternative_location => {
741 self.definition_location = DefinitionLocation::Alternative {
742 location,
743 first_alternative_location: alternative_location,
744 };
745 }
746
747 DefinitionLocation::Regular { .. } | DefinitionLocation::Alternative { .. } => (),
748 };
749 }
750
751 pub fn find_in_module(mut self, module: &TypedModule) -> HashSet<VariableReference> {
752 self.visit_typed_module(module);
753 self.references
754 }
755
756 pub fn find(mut self, expression: &TypedExpr) -> HashSet<VariableReference> {
757 self.visit_typed_expr(expression);
758 self.references
759 }
760
761 fn register_alternative_definition(&mut self, name: &EcoString, location: &SrcSpan) {
762 match self.alternative_variable {
763 // If we are inside the same alternative pattern as the target
764 // variable and the name is the same, this is an alternative definition
765 // of the same variable. We don't register the reference if this is
766 // the exact variable though, as that would result in a duplicated
767 // reference.
768 AlternativeVariable::Track
769 if *name == self.name && *location != self.definition_location() =>
770 {
771 self.update_alternative_origin(*location);
772
773 _ = self.references.insert(VariableReference {
774 location: *location,
775 kind: VariableReferenceKind::Variable,
776 });
777 }
778 AlternativeVariable::Track | AlternativeVariable::Ignore => {}
779 }
780 }
781}
782
783impl<'ast> Visit<'ast> for FindVariableReferences {
784 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
785 if fun
786 .full_location()
787 .contains(self.definition_origin_location().start)
788 {
789 ast::visit::visit_typed_function(self, fun);
790 }
791 }
792
793 fn visit_typed_expr_var(
794 &mut self,
795 location: &'ast SrcSpan,
796 constructor: &'ast ValueConstructor,
797 _name: &'ast EcoString,
798 ) {
799 match constructor.variant {
800 ValueConstructorVariant::LocalVariable {
801 location: definition_location,
802 ..
803 } if definition_location == self.definition_origin_location() => {
804 _ = self.references.insert(VariableReference {
805 location: *location,
806 kind: VariableReferenceKind::Variable,
807 });
808 }
809 ValueConstructorVariant::LocalVariable { .. }
810 | ValueConstructorVariant::ModuleConstant { .. }
811 | ValueConstructorVariant::ModuleFn { .. }
812 | ValueConstructorVariant::Record { .. } => {}
813 }
814 }
815
816 fn visit_typed_clause_guard_var(
817 &mut self,
818 location: &'ast SrcSpan,
819 _name: &'ast EcoString,
820 _type_: &'ast std::sync::Arc<Type>,
821 definition_location: &'ast SrcSpan,
822 _origin: &'ast VariableOrigin,
823 ) {
824 if *definition_location == self.definition_origin_location() {
825 _ = self.references.insert(VariableReference {
826 location: *location,
827 kind: VariableReferenceKind::Variable,
828 });
829 }
830 }
831
832 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
833 // If this alternative pattern contains the variable we are finding
834 // references for, we track that so we can find alternative definitions
835 // of the target variable.
836 if clause
837 .pattern_location()
838 .contains(self.definition_origin_location().start)
839 {
840 self.alternative_variable = AlternativeVariable::Track;
841 }
842
843 for pattern in clause.pattern.iter() {
844 self.visit_typed_pattern(pattern);
845 }
846 for patterns in clause.alternative_patterns.iter() {
847 for pattern in patterns {
848 self.visit_typed_pattern(pattern);
849 }
850 }
851
852 self.alternative_variable = AlternativeVariable::Ignore;
853
854 if let Some(guard) = &clause.guard {
855 self.visit_typed_clause_guard(guard);
856 }
857 self.visit_typed_expr(&clause.then);
858 }
859
860 fn visit_typed_pattern_variable(
861 &mut self,
862 location: &'ast SrcSpan,
863 name: &'ast EcoString,
864 _type_: &'ast std::sync::Arc<Type>,
865 _origin: &'ast VariableOrigin,
866 ) {
867 self.register_alternative_definition(name, location);
868 }
869
870 fn visit_typed_pattern_assign(
871 &mut self,
872 location: &'ast SrcSpan,
873 name: &'ast EcoString,
874 pattern: &'ast ast::TypedPattern,
875 ) {
876 self.register_alternative_definition(name, location);
877
878 ast::visit::visit_typed_pattern_assign(self, location, name, pattern);
879 }
880
881 fn visit_typed_bit_array_size_variable(
882 &mut self,
883 location: &'ast SrcSpan,
884 _name: &'ast EcoString,
885 constructor: &'ast Option<Box<ValueConstructor>>,
886 _type_: &'ast std::sync::Arc<Type>,
887 ) {
888 let variant = match constructor {
889 Some(constructor) => &constructor.variant,
890 None => return,
891 };
892 match variant {
893 ValueConstructorVariant::LocalVariable {
894 location: definition_location,
895 ..
896 } if *definition_location == self.definition_origin_location() => {
897 _ = self.references.insert(VariableReference {
898 location: *location,
899 kind: VariableReferenceKind::Variable,
900 });
901 }
902 ValueConstructorVariant::LocalVariable { .. }
903 | ValueConstructorVariant::ModuleConstant { .. }
904 | ValueConstructorVariant::ModuleFn { .. }
905 | ValueConstructorVariant::Record { .. } => {}
906 }
907 }
908
909 fn visit_typed_call_arg(&mut self, arg: &'ast gleam_core::type_::TypedCallArg) {
910 if let TypedExpr::Var {
911 location,
912 constructor,
913 ..
914 } = &arg.value
915 {
916 match &constructor.variant {
917 ValueConstructorVariant::LocalVariable {
918 location: definition_location,
919 ..
920 } if arg.uses_label_shorthand()
921 && *definition_location == self.definition_origin_location() =>
922 {
923 _ = self.references.insert(VariableReference {
924 location: *location,
925 kind: VariableReferenceKind::LabelShorthand,
926 });
927 return;
928 }
929 ValueConstructorVariant::LocalVariable { .. }
930 | ValueConstructorVariant::ModuleConstant { .. }
931 | ValueConstructorVariant::ModuleFn { .. }
932 | ValueConstructorVariant::Record { .. } => {}
933 }
934 }
935
936 ast::visit::visit_typed_call_arg(self, arg);
937 }
938
939 fn visit_typed_pattern_string_prefix(
940 &mut self,
941 location: &'ast SrcSpan,
942 left_location: &'ast SrcSpan,
943 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>,
944 right_location: &'ast SrcSpan,
945 left_side_string: &'ast EcoString,
946 right_side_assignment: &'ast AssignName,
947 ) {
948 // Handle the prefix alias in alternative pattern: "prefix" as name | "other_prefix" as name
949 if let Some((name, left_side_assignment_location)) = left_side_assignment {
950 self.register_alternative_definition(name, left_side_assignment_location);
951 }
952
953 // Handle the suffix in alternative pattern: "prefix" <> name | "other_prefix" <> name
954 match right_side_assignment {
955 AssignName::Variable(name) => {
956 self.register_alternative_definition(name, right_location)
957 }
958 AssignName::Discard(_) => {}
959 }
960
961 ast::visit::visit_typed_pattern_string_prefix(
962 self,
963 location,
964 left_location,
965 left_side_assignment,
966 right_location,
967 left_side_string,
968 right_side_assignment,
969 );
970 }
971}
972
973pub struct FindTypeVariableReferences<'a> {
974 pub references: Vec<SrcSpan>,
975 pub name: &'a EcoString,
976 pub location: SrcSpan,
977}
978
979impl<'a> FindTypeVariableReferences<'a> {
980 pub fn find_in_module(
981 module: &TypedModule,
982 type_variable_location: SrcSpan,
983 type_variable_name: &'a EcoString,
984 ) -> Vec<SrcSpan> {
985 let mut finder = Self {
986 references: Vec::new(),
987 name: type_variable_name,
988 location: type_variable_location,
989 };
990 finder.visit_typed_module(module);
991 finder.references
992 }
993}
994
995impl<'ast> Visit<'ast> for FindTypeVariableReferences<'_> {
996 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
997 if fun.full_location().contains_span(self.location) {
998 ast::visit::visit_typed_function(self, fun);
999 }
1000 }
1001
1002 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
1003 if custom_type.full_location().contains_span(self.location) {
1004 for (location, name) in custom_type.parameters.iter() {
1005 if name == self.name {
1006 self.references.push(*location)
1007 }
1008 }
1009 ast::visit::visit_typed_custom_type(self, custom_type);
1010 }
1011 }
1012
1013 fn visit_typed_module_constant(&mut self, constant: &'ast ast::TypedModuleConstant) {
1014 if constant.location.contains_span(self.location) {
1015 ast::visit::visit_typed_module_constant(self, constant);
1016 }
1017 }
1018
1019 fn visit_typed_type_alias(&mut self, type_alias: &'ast ast::TypedTypeAlias) {
1020 if type_alias.location.contains_span(self.location) {
1021 for (location, name) in type_alias.parameters.iter() {
1022 if name == self.name {
1023 self.references.push(*location)
1024 }
1025 }
1026 ast::visit::visit_typed_type_alias(self, type_alias);
1027 }
1028 }
1029
1030 fn visit_type_ast_var(&mut self, location: &'ast SrcSpan, name: &'ast EcoString) {
1031 if name == self.name {
1032 self.references.push(*location);
1033 }
1034 }
1035}