@@ -180,11 +180,12 @@ type returnVariable struct {
180
180
// of the function and insert this call as well as the extracted function into
181
181
// their proper locations.
182
182
func extractFunction (fset * token.FileSet , rng span.Range , src []byte , file * ast.File , pkg * types.Package , info * types.Info ) (* analysis.SuggestedFix , error ) {
183
- tok , path , rng , outer , start , ok , err := canExtractFunction (fset , rng , src , file , info )
183
+ p , ok , err := canExtractFunction (fset , rng , src , file , info )
184
184
if ! ok {
185
185
return nil , fmt .Errorf ("extractFunction: cannot extract %s: %v" ,
186
186
fset .Position (rng .Start ), err )
187
187
}
188
+ tok , path , rng , outer , start := p .tok , p .path , p .rng , p .outer , p .start
188
189
fileScope := info .Scopes [file ]
189
190
if fileScope == nil {
190
191
return nil , fmt .Errorf ("extractFunction: file scope is empty" )
@@ -229,8 +230,10 @@ func extractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.
229
230
// we must determine the signature of the extracted function. We will then replace
230
231
// the block with an assignment statement that calls the extracted function with
231
232
// the appropriate parameters and return values.
232
- free , vars , assigned , defined := collectFreeVars (
233
- info , file , fileScope , pkgScope , rng , path [0 ])
233
+ variables , err := collectFreeVars (info , file , fileScope , pkgScope , rng , path [0 ])
234
+ if err != nil {
235
+ return nil , err
236
+ }
234
237
235
238
var (
236
239
params , returns []ast.Expr // used when calling the extracted function
@@ -269,42 +272,38 @@ func extractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.
269
272
// variable in the extracted function. Determine the outcome(s) for each variable
270
273
// based on whether it is free, altered within the selected block, and used outside
271
274
// of the selected block.
272
- for _ , obj := range vars {
273
- if _ , ok := seenVars [obj ]; ok {
275
+ for _ , v := range variables {
276
+ if _ , ok := seenVars [v . obj ]; ok {
274
277
continue
275
278
}
276
- typ := analysisinternal .TypeExpr (fset , file , pkg , obj .Type ())
279
+ typ := analysisinternal .TypeExpr (fset , file , pkg , v . obj .Type ())
277
280
if typ == nil {
278
- return nil , fmt .Errorf ("nil AST expression for type: %v" , obj .Name ())
281
+ return nil , fmt .Errorf ("nil AST expression for type: %v" , v . obj .Name ())
279
282
}
280
- seenVars [obj ] = typ
281
- identifier := ast .NewIdent (obj .Name ())
283
+ seenVars [v . obj ] = typ
284
+ identifier := ast .NewIdent (v . obj .Name ())
282
285
// An identifier must meet three conditions to become a return value of the
283
286
// extracted function. (1) its value must be defined or reassigned within
284
287
// the selection (isAssigned), (2) it must be used at least once after the
285
288
// selection (isUsed), and (3) its first use after the selection
286
289
// cannot be its own reassignment or redefinition (objOverriden).
287
- if obj .Parent () == nil {
290
+ if v . obj .Parent () == nil {
288
291
return nil , fmt .Errorf ("parent nil" )
289
292
}
290
- isUsed , firstUseAfter :=
291
- objUsed (info , span .NewRange (fset , rng .End , obj .Parent ().End ()), obj )
292
- _ , isAssigned := assigned [obj ]
293
- _ , isFree := free [obj ]
294
- if isAssigned && isUsed && ! varOverridden (info , firstUseAfter , obj , isFree , outer ) {
293
+ isUsed , firstUseAfter := objUsed (info , span .NewRange (fset , rng .End , v .obj .Parent ().End ()), v .obj )
294
+ if v .assigned && isUsed && ! varOverridden (info , firstUseAfter , v .obj , v .free , outer ) {
295
295
returnTypes = append (returnTypes , & ast.Field {Type : typ })
296
296
returns = append (returns , identifier )
297
- if ! isFree {
298
- uninitialized = append (uninitialized , obj )
299
- } else if obj .Parent ().Pos () == startParent .Pos () {
297
+ if ! v . free {
298
+ uninitialized = append (uninitialized , v . obj )
299
+ } else if v . obj .Parent ().Pos () == startParent .Pos () {
300
300
canRedefineCount ++
301
301
}
302
302
}
303
- _ , isDefined := defined [obj ]
304
303
// An identifier must meet two conditions to become a parameter of the
305
304
// extracted function. (1) it must be free (isFree), and (2) its first
306
305
// use within the selection cannot be its own definition (isDefined).
307
- if isFree && ! isDefined {
306
+ if v . free && ! v . defined {
308
307
params = append (params , identifier )
309
308
paramTypes = append (paramTypes , & ast.Field {
310
309
Names : []* ast.Ident {identifier },
@@ -409,8 +408,7 @@ func extractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.
409
408
// statements in the selection. Update the type signature of the extracted
410
409
// function and construct the if statement that will be inserted in the enclosing
411
410
// function.
412
- retVars , ifReturn , err = generateReturnInfo (
413
- enclosing , pkg , path , file , info , fset , rng .Start )
411
+ retVars , ifReturn , err = generateReturnInfo (enclosing , pkg , path , file , info , fset , rng .Start )
414
412
if err != nil {
415
413
return nil , err
416
414
}
@@ -500,13 +498,11 @@ func extractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.
500
498
fullReplacement .Write (newFuncBuf .Bytes ()) // insert the extracted function
501
499
502
500
return & analysis.SuggestedFix {
503
- TextEdits : []analysis.TextEdit {
504
- {
505
- Pos : outer .Pos (),
506
- End : outer .End (),
507
- NewText : []byte (fullReplacement .String ()),
508
- },
509
- },
501
+ TextEdits : []analysis.TextEdit {{
502
+ Pos : outer .Pos (),
503
+ End : outer .End (),
504
+ NewText : []byte (fullReplacement .String ()),
505
+ }},
510
506
}, nil
511
507
}
512
508
@@ -561,15 +557,28 @@ func findParent(start ast.Node, target ast.Node) ast.Node {
561
557
return parent
562
558
}
563
559
560
+ // variable describes the status of a variable within a selection.
561
+ type variable struct {
562
+ obj types.Object
563
+
564
+ // free reports whether the variable is a free variable, meaning it should
565
+ // be a parameter to the extracted function.
566
+ free bool
567
+
568
+ // assigned reports whether the variable is assigned to in the selection.
569
+ assigned bool
570
+
571
+ // defined reports whether the variable is defined in the selection.
572
+ defined bool
573
+ }
574
+
564
575
// collectFreeVars maps each identifier in the given range to whether it is "free."
565
576
// Given a range, a variable in that range is defined as "free" if it is declared
566
577
// outside of the range and neither at the file scope nor package scope. These free
567
578
// variables will be used as arguments in the extracted function. It also returns a
568
579
// list of identifiers that may need to be returned by the extracted function.
569
580
// Some of the code in this function has been adapted from tools/cmd/guru/freevars.go.
570
- func collectFreeVars (info * types.Info , file * ast.File , fileScope * types.Scope ,
571
- pkgScope * types.Scope , rng span.Range , node ast.Node ) (map [types.Object ]struct {},
572
- []types.Object , map [types.Object ]struct {}, map [types.Object ]struct {}) {
581
+ func collectFreeVars (info * types.Info , file * ast.File , fileScope , pkgScope * types.Scope , rng span.Range , node ast.Node ) ([]* variable , error ) {
573
582
// id returns non-nil if n denotes an object that is referenced by the span
574
583
// and defined either within the span or in the lexical environment. The bool
575
584
// return value acts as an indicator for where it was defined.
@@ -612,7 +621,7 @@ func collectFreeVars(info *types.Info, file *ast.File, fileScope *types.Scope,
612
621
}
613
622
return nil , false
614
623
}
615
- free := make (map [types.Object ]struct {} )
624
+ seen := make (map [types.Object ]* variable )
616
625
firstUseIn := make (map [types.Object ]token.Pos )
617
626
var vars []types.Object
618
627
ast .Inspect (node , func (n ast.Node ) bool {
@@ -630,15 +639,16 @@ func collectFreeVars(info *types.Info, file *ast.File, fileScope *types.Scope,
630
639
prune = true
631
640
}
632
641
if obj != nil {
633
- if isFree {
634
- free [obj ] = struct {}{}
642
+ seen [obj ] = & variable {
643
+ obj : obj ,
644
+ free : isFree ,
635
645
}
646
+ vars = append (vars , obj )
636
647
// Find the first time that the object is used in the selection.
637
648
first , ok := firstUseIn [obj ]
638
649
if ! ok || n .Pos () < first {
639
650
firstUseIn [obj ] = n .Pos ()
640
651
}
641
- vars = append (vars , obj )
642
652
if prune {
643
653
return false
644
654
}
@@ -657,8 +667,6 @@ func collectFreeVars(info *types.Info, file *ast.File, fileScope *types.Scope,
657
667
// 3: y := 3
658
668
// 4: z := x + a
659
669
//
660
- assigned := make (map [types.Object ]struct {})
661
- defined := make (map [types.Object ]struct {})
662
670
ast .Inspect (node , func (n ast.Node ) bool {
663
671
if n == nil {
664
672
return false
@@ -677,7 +685,10 @@ func collectFreeVars(info *types.Info, file *ast.File, fileScope *types.Scope,
677
685
if obj == nil {
678
686
continue
679
687
}
680
- assigned [obj ] = struct {}{}
688
+ if _ , ok := seen [obj ]; ! ok {
689
+ continue
690
+ }
691
+ seen [obj ].assigned = true
681
692
if n .Tok != token .DEFINE {
682
693
continue
683
694
}
@@ -697,7 +708,10 @@ func collectFreeVars(info *types.Info, file *ast.File, fileScope *types.Scope,
697
708
if referencesObj (info , expr , obj ) {
698
709
continue
699
710
}
700
- defined [obj ] = struct {}{}
711
+ if _ , ok := seen [obj ]; ! ok {
712
+ continue
713
+ }
714
+ seen [obj ].defined = true
701
715
break
702
716
}
703
717
}
@@ -717,7 +731,10 @@ func collectFreeVars(info *types.Info, file *ast.File, fileScope *types.Scope,
717
731
if obj == nil {
718
732
continue
719
733
}
720
- assigned [obj ] = struct {}{}
734
+ if _ , ok := seen [obj ]; ! ok {
735
+ continue
736
+ }
737
+ seen [obj ].assigned = true
721
738
}
722
739
}
723
740
return false
@@ -727,12 +744,23 @@ func collectFreeVars(info *types.Info, file *ast.File, fileScope *types.Scope,
727
744
} else if obj , _ := id (ident ); obj == nil {
728
745
return false
729
746
} else {
730
- assigned [obj ] = struct {}{}
747
+ if _ , ok := seen [obj ]; ! ok {
748
+ return false
749
+ }
750
+ seen [obj ].assigned = true
731
751
}
732
752
}
733
753
return true
734
754
})
735
- return free , vars , assigned , defined
755
+ var variables []* variable
756
+ for _ , obj := range vars {
757
+ v , ok := seen [obj ]
758
+ if ! ok {
759
+ return nil , fmt .Errorf ("no seen types.Object for %v" , obj )
760
+ }
761
+ variables = append (variables , v )
762
+ }
763
+ return variables , nil
736
764
}
737
765
738
766
// referencesObj checks whether the given object appears in the given expression.
@@ -756,29 +784,34 @@ func referencesObj(info *types.Info, expr ast.Expr, obj types.Object) bool {
756
784
return hasObj
757
785
}
758
786
759
- // canExtractFunction reports whether the code in the given range can be extracted to a function.
760
- func canExtractFunction (fset * token.FileSet , rng span.Range , src []byte , file * ast.File , info * types.Info ) (* token.File , []ast.Node , span.Range , * ast.FuncDecl , ast.Node , bool , error ) {
787
+ type fnExtractParams struct {
788
+ tok * token.File
789
+ path []ast.Node
790
+ rng span.Range
791
+ outer * ast.FuncDecl
792
+ start ast.Node
793
+ }
794
+
795
+ // canExtractFunction reports whether the code in the given range can be
796
+ // extracted to a function.
797
+ func canExtractFunction (fset * token.FileSet , rng span.Range , src []byte , file * ast.File , info * types.Info ) (* fnExtractParams , bool , error ) {
761
798
if rng .Start == rng .End {
762
- return nil , nil , span.Range {}, nil , nil , false ,
763
- fmt .Errorf ("start and end are equal" )
799
+ return nil , false , fmt .Errorf ("start and end are equal" )
764
800
}
765
801
tok := fset .File (file .Pos ())
766
802
if tok == nil {
767
- return nil , nil , span.Range {}, nil , nil , false ,
768
- fmt .Errorf ("no file for pos %v" , fset .Position (file .Pos ()))
803
+ return nil , false , fmt .Errorf ("no file for pos %v" , fset .Position (file .Pos ()))
769
804
}
770
805
rng = adjustRangeForWhitespace (rng , tok , src )
771
806
path , _ := astutil .PathEnclosingInterval (file , rng .Start , rng .End )
772
807
if len (path ) == 0 {
773
- return nil , nil , span.Range {}, nil , nil , false ,
774
- fmt .Errorf ("no path enclosing interval" )
808
+ return nil , false , fmt .Errorf ("no path enclosing interval" )
775
809
}
776
810
// Node that encloses the selection must be a statement.
777
811
// TODO: Support function extraction for an expression.
778
812
_ , ok := path [0 ].(ast.Stmt )
779
813
if ! ok {
780
- return nil , nil , span.Range {}, nil , nil , false ,
781
- fmt .Errorf ("node is not a statement" )
814
+ return nil , false , fmt .Errorf ("node is not a statement" )
782
815
}
783
816
784
817
// Find the function declaration that encloses the selection.
@@ -790,7 +823,7 @@ func canExtractFunction(fset *token.FileSet, rng span.Range, src []byte, file *a
790
823
}
791
824
}
792
825
if outer == nil {
793
- return nil , nil , span. Range {}, nil , nil , false , fmt .Errorf ("no enclosing function" )
826
+ return nil , false , fmt .Errorf ("no enclosing function" )
794
827
}
795
828
796
829
// Find the nodes at the start and end of the selection.
@@ -799,8 +832,8 @@ func canExtractFunction(fset *token.FileSet, rng span.Range, src []byte, file *a
799
832
if n == nil {
800
833
return false
801
834
}
802
- // Do not override 'start' with a node that begins at the same location but is
803
- // nested further from 'outer'.
835
+ // Do not override 'start' with a node that begins at the same location
836
+ // but is nested further from 'outer'.
804
837
if start == nil && n .Pos () == rng .Start && n .End () <= rng .End {
805
838
start = n
806
839
}
@@ -810,10 +843,15 @@ func canExtractFunction(fset *token.FileSet, rng span.Range, src []byte, file *a
810
843
return n .Pos () <= rng .End
811
844
})
812
845
if start == nil || end == nil {
813
- return nil , nil , span.Range {}, nil , nil , false ,
814
- fmt .Errorf ("range does not map to AST nodes" )
846
+ return nil , false , fmt .Errorf ("range does not map to AST nodes" )
815
847
}
816
- return tok , path , rng , outer , start , true , nil
848
+ return & fnExtractParams {
849
+ tok : tok ,
850
+ path : path ,
851
+ rng : rng ,
852
+ outer : outer ,
853
+ start : start ,
854
+ }, true , nil
817
855
}
818
856
819
857
// objUsed checks if the object is used within the range. It returns the first occurence of
0 commit comments