@@ -366,21 +366,128 @@ pub struct FindVariableReferences {
366366 // To avoid this, we use a `HashSet` instead of a `Vec` here.
367367 // See: https://github.com/gleam-lang/gleam/issues/4859 and the linked PR.
368368 references : HashSet < VariableReference > ,
369- definition_location : SrcSpan ,
369+ definition_location : DefinitionLocation ,
370370 alternative_variable : AlternativeVariable ,
371371 name : EcoString ,
372372}
373373
374+ /// Where the variable we're finding references for is defined.
375+ ///
376+ enum DefinitionLocation {
377+ /// This is the location where the variable is defined, nothing special is
378+ /// going on here. For example:
379+ ///
380+ /// ```gleam
381+ /// let wibble = 1
382+ /// // ^^^^^^ Definition location for `wibble`
383+ /// wibble + 1
384+ /// ^^^^^^
385+ /// // `wibble` used here, defined earlier
386+ /// ```
387+ ///
388+ Regular { location : SrcSpan } ,
389+
390+ /// When dealing with alternative patterns and aliases we need special care:
391+ /// each usage wil always reference the first alternative where a variable
392+ /// is defined and not the following ones. For example:
393+ ///
394+ /// ```gleam
395+ /// case wibble {
396+ /// [] as var | [_] as var -> var
397+ /// // ^^^ ^^^ If we look where `var` thinks it's defined
398+ /// // It will say it's defined here!
399+ /// }
400+ /// ```
401+ ///
402+ /// This poses a problem if we start the renaming from the second
403+ /// alternative pattern:
404+ ///
405+ /// ```gleam
406+ /// case wibble {
407+ /// [] as var | [_] as var -> var
408+ /// // ^^^ Since `var` uses the first alternative as its
409+ /// // definition location, this would not be considered
410+ /// // a reference to that same var.
411+ /// }
412+ /// ```
413+ ///
414+ /// So we keep track of the location of this definition, but we also need
415+ /// to store the location of the first definition in the alternative case
416+ /// (that's `first_alternative_location`), so that when we look for
417+ /// references we can check against this one that is canonically used by
418+ /// expressions in the AST
419+ ///
420+ Alternative {
421+ location : SrcSpan ,
422+ first_alternative_location : SrcSpan ,
423+ } ,
424+ }
425+
374426impl FindVariableReferences {
375427 pub fn new ( variable_definition_location : SrcSpan , variable_name : EcoString ) -> Self {
376428 Self {
377429 references : HashSet :: new ( ) ,
378- definition_location : variable_definition_location,
430+ definition_location : DefinitionLocation :: Regular {
431+ location : variable_definition_location,
432+ } ,
379433 alternative_variable : AlternativeVariable :: Ignore ,
380434 name : variable_name,
381435 }
382436 }
383437
438+ /// Where the definition for which we're accumulating references is
439+ /// originally defined. In case of alternative patterns this will point to
440+ /// the first occurrence of that name! Look at the docs for
441+ /// `DefinitionLocation` to learn more on why this is needed.
442+ ///
443+ fn definition_origin_location ( & self ) -> SrcSpan {
444+ match self . definition_location {
445+ DefinitionLocation :: Regular { location }
446+ | DefinitionLocation :: Alternative {
447+ first_alternative_location : location,
448+ ..
449+ } => location,
450+ }
451+ }
452+
453+ /// This is the location of the definition for which we're accumulating
454+ /// references. In most cases you'll want to use `definition_origin_location`.
455+ /// The difference between the two is explained in greater detail in the docs
456+ /// for `DefinitionLocation`.
457+ ///
458+ fn definition_location ( & self ) -> SrcSpan {
459+ match self . definition_location {
460+ DefinitionLocation :: Regular { location }
461+ | DefinitionLocation :: Alternative { location, .. } => location,
462+ }
463+ }
464+
465+ fn update_alternative_origin ( & mut self , alternative_location : SrcSpan ) {
466+ match self . definition_location {
467+ // We've found the location of the origin of an alternative pattern.
468+ DefinitionLocation :: Regular { location } if alternative_location < location => {
469+ self . definition_location = DefinitionLocation :: Alternative {
470+ location,
471+ first_alternative_location : alternative_location,
472+ } ;
473+ }
474+
475+ // Since the new alternative location we've found is smaller, that
476+ // is the actual first one for the alternative pattern!
477+ DefinitionLocation :: Alternative {
478+ location,
479+ first_alternative_location,
480+ } if alternative_location < first_alternative_location => {
481+ self . definition_location = DefinitionLocation :: Alternative {
482+ location,
483+ first_alternative_location : alternative_location,
484+ } ;
485+ }
486+
487+ _ => ( ) ,
488+ } ;
489+ }
490+
384491 pub fn find_in_module ( mut self , module : & TypedModule ) -> HashSet < VariableReference > {
385492 self . visit_typed_module ( module) ;
386493 self . references
@@ -394,7 +501,10 @@ impl FindVariableReferences {
394501
395502impl < ' ast > Visit < ' ast > for FindVariableReferences {
396503 fn visit_typed_function ( & mut self , fun : & ' ast ast:: TypedFunction ) {
397- if fun. full_location ( ) . contains ( self . definition_location . start ) {
504+ if fun
505+ . full_location ( )
506+ . contains ( self . definition_origin_location ( ) . start )
507+ {
398508 ast:: visit:: visit_typed_function ( self , fun) ;
399509 }
400510 }
@@ -409,7 +519,7 @@ impl<'ast> Visit<'ast> for FindVariableReferences {
409519 ValueConstructorVariant :: LocalVariable {
410520 location : definition_location,
411521 ..
412- } if definition_location == self . definition_location => {
522+ } if definition_location == self . definition_origin_location ( ) => {
413523 _ = self . references . insert ( VariableReference {
414524 location : * location,
415525 kind : VariableReferenceKind :: Variable ,
@@ -426,7 +536,7 @@ impl<'ast> Visit<'ast> for FindVariableReferences {
426536 _type_ : & ' ast std:: sync:: Arc < Type > ,
427537 definition_location : & ' ast SrcSpan ,
428538 ) {
429- if * definition_location == self . definition_location {
539+ if * definition_location == self . definition_origin_location ( ) {
430540 _ = self . references . insert ( VariableReference {
431541 location : * location,
432542 kind : VariableReferenceKind :: Variable ,
@@ -440,7 +550,7 @@ impl<'ast> Visit<'ast> for FindVariableReferences {
440550 // of the target variable.
441551 if clause
442552 . pattern_location ( )
443- . contains ( self . definition_location . start )
553+ . contains ( self . definition_origin_location ( ) . start )
444554 {
445555 self . alternative_variable = AlternativeVariable :: Track ;
446556 }
@@ -476,15 +586,45 @@ impl<'ast> Visit<'ast> for FindVariableReferences {
476586 // the exact variable though, as that would result in a duplicated
477587 // reference.
478588 AlternativeVariable :: Track
479- if * name == self . name && * location != self . definition_location =>
589+ if * name == self . name && * location != self . definition_location ( ) =>
480590 {
591+ self . update_alternative_origin ( * location) ;
592+
593+ _ = self . references . insert ( VariableReference {
594+ location : * location,
595+ kind : VariableReferenceKind :: Variable ,
596+ } ) ;
597+ }
598+ AlternativeVariable :: Track | AlternativeVariable :: Ignore => { }
599+ }
600+ }
601+
602+ fn visit_typed_pattern_assign (
603+ & mut self ,
604+ location : & ' ast SrcSpan ,
605+ name : & ' ast EcoString ,
606+ pattern : & ' ast ast:: TypedPattern ,
607+ ) {
608+ match self . alternative_variable {
609+ // If we are inside the same alternative pattern as the target
610+ // variable and the name is the same, this is an alternative definition
611+ // of the same variable. We don't register the reference if this is
612+ // the exact variable though, as that would result in a duplicated
613+ // reference.
614+ AlternativeVariable :: Track
615+ if * name == self . name && * location != self . definition_location ( ) =>
616+ {
617+ self . update_alternative_origin ( * location) ;
618+
481619 _ = self . references . insert ( VariableReference {
482620 location : * location,
483621 kind : VariableReferenceKind :: Variable ,
484622 } ) ;
485623 }
486624 AlternativeVariable :: Track | AlternativeVariable :: Ignore => { }
487625 }
626+
627+ ast:: visit:: visit_typed_pattern_assign ( self , location, name, pattern) ;
488628 }
489629
490630 fn visit_typed_bit_array_size_variable (
@@ -502,7 +642,7 @@ impl<'ast> Visit<'ast> for FindVariableReferences {
502642 ValueConstructorVariant :: LocalVariable {
503643 location : definition_location,
504644 ..
505- } if * definition_location == self . definition_location => {
645+ } if * definition_location == self . definition_origin_location ( ) => {
506646 _ = self . references . insert ( VariableReference {
507647 location : * location,
508648 kind : VariableReferenceKind :: Variable ,
@@ -524,7 +664,7 @@ impl<'ast> Visit<'ast> for FindVariableReferences {
524664 location : definition_location,
525665 ..
526666 } if arg. uses_label_shorthand ( )
527- && * definition_location == self . definition_location =>
667+ && * definition_location == self . definition_origin_location ( ) =>
528668 {
529669 _ = self . references . insert ( VariableReference {
530670 location : * location,
0 commit comments