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