@@ -37,22 +37,22 @@ func extractVariable(fset *token.FileSet, start, end token.Pos, src []byte, file
3737 // TODO: stricter rules for selectorExpr.
3838 case * ast.BasicLit , * ast.CompositeLit , * ast.IndexExpr , * ast.SliceExpr ,
3939 * ast.UnaryExpr , * ast.BinaryExpr , * ast.SelectorExpr :
40- lhsName , _ := generateAvailableIdentifier (expr .Pos (), path , pkg , info , "x" , 0 )
40+ lhsName , _ := generateAvailableName (expr .Pos (), path , pkg , info , "x" , 0 )
4141 lhsNames = append (lhsNames , lhsName )
4242 case * ast.CallExpr :
4343 tup , ok := info .TypeOf (expr ).(* types.Tuple )
4444 if ! ok {
4545 // If the call expression only has one return value, we can treat it the
4646 // same as our standard extract variable case.
47- lhsName , _ := generateAvailableIdentifier (expr .Pos (), path , pkg , info , "x" , 0 )
47+ lhsName , _ := generateAvailableName (expr .Pos (), path , pkg , info , "x" , 0 )
4848 lhsNames = append (lhsNames , lhsName )
4949 break
5050 }
5151 idx := 0
5252 for i := 0 ; i < tup .Len (); i ++ {
5353 // Generate a unique variable for each return value.
5454 var lhsName string
55- lhsName , idx = generateAvailableIdentifier (expr .Pos (), path , pkg , info , "x" , idx )
55+ lhsName , idx = generateAvailableName (expr .Pos (), path , pkg , info , "x" , idx )
5656 lhsNames = append (lhsNames , lhsName )
5757 }
5858 default :
@@ -150,12 +150,12 @@ func calculateIndentation(content []byte, tok *token.File, insertBeforeStmt ast.
150150 return string (content [lineOffset :stmtOffset ]), nil
151151}
152152
153- // generateAvailableIdentifier adjusts the new function name until there are no collisions in scope.
153+ // generateAvailableName adjusts the new function name until there are no collisions in scope.
154154// Possible collisions include other function and variable names. Returns the next index to check for prefix.
155- func generateAvailableIdentifier (pos token.Pos , path []ast.Node , pkg * types.Package , info * types.Info , prefix string , idx int ) (string , int ) {
155+ func generateAvailableName (pos token.Pos , path []ast.Node , pkg * types.Package , info * types.Info , prefix string , idx int ) (string , int ) {
156156 scopes := CollectScopes (info , path , pos )
157157 scopes = append (scopes , pkg .Scope ())
158- return generateIdentifier (idx , prefix , func (name string ) bool {
158+ return generateName (idx , prefix , func (name string ) bool {
159159 for _ , scope := range scopes {
160160 if scope != nil && scope .Lookup (name ) != nil {
161161 return true
@@ -165,7 +165,31 @@ func generateAvailableIdentifier(pos token.Pos, path []ast.Node, pkg *types.Pack
165165 })
166166}
167167
168- func generateIdentifier (idx int , prefix string , hasCollision func (string ) bool ) (string , int ) {
168+ // generateNameOutsideOfRange is like generateAvailableName, but ignores names
169+ // declared between start and end for the purposes of detecting conflicts.
170+ //
171+ // This is used for function extraction, where [start, end) will be extracted
172+ // to a new scope.
173+ func generateNameOutsideOfRange (start , end token.Pos , path []ast.Node , pkg * types.Package , info * types.Info , prefix string , idx int ) (string , int ) {
174+ scopes := CollectScopes (info , path , start )
175+ scopes = append (scopes , pkg .Scope ())
176+ return generateName (idx , prefix , func (name string ) bool {
177+ for _ , scope := range scopes {
178+ if scope != nil {
179+ if obj := scope .Lookup (name ); obj != nil {
180+ // Only report a collision if the object declaration was outside the
181+ // extracted range.
182+ if obj .Pos () < start || end <= obj .Pos () {
183+ return true
184+ }
185+ }
186+ }
187+ }
188+ return false
189+ })
190+ }
191+
192+ func generateName (idx int , prefix string , hasCollision func (string ) bool ) (string , int ) {
169193 name := prefix
170194 if idx != 0 {
171195 name += fmt .Sprintf ("%d" , idx )
@@ -182,7 +206,7 @@ func generateIdentifier(idx int, prefix string, hasCollision func(string) bool)
182206type returnVariable struct {
183207 // name is the identifier that is used on the left-hand side of the call to
184208 // the extracted function.
185- name ast.Expr
209+ name * ast.Ident
186210 // decl is the declaration of the variable. It is used in the type signature of the
187211 // extracted function and for variable declarations.
188212 decl * ast.Field
@@ -517,7 +541,7 @@ func extractFunctionMethod(fset *token.FileSet, start, end token.Pos, src []byte
517541 // statements in the selection. Update the type signature of the extracted
518542 // function and construct the if statement that will be inserted in the enclosing
519543 // function.
520- retVars , ifReturn , err = generateReturnInfo (enclosing , pkg , path , file , info , start , hasNonNestedReturn )
544+ retVars , ifReturn , err = generateReturnInfo (enclosing , pkg , path , file , info , start , end , hasNonNestedReturn )
521545 if err != nil {
522546 return nil , nil , err
523547 }
@@ -552,7 +576,7 @@ func extractFunctionMethod(fset *token.FileSet, start, end token.Pos, src []byte
552576 funName = name
553577 } else {
554578 name = "newFunction"
555- funName , _ = generateAvailableIdentifier (start , path , pkg , info , name , 0 )
579+ funName , _ = generateAvailableName (start , path , pkg , info , name , 0 )
556580 }
557581 extractedFunCall := generateFuncCall (hasNonNestedReturn , hasReturnValues , params ,
558582 append (returns , getNames (retVars )... ), funName , sym , receiverName )
@@ -1187,12 +1211,12 @@ func parseBlockStmt(fset *token.FileSet, src []byte) (*ast.BlockStmt, error) {
11871211// signature of the extracted function. We prepare names, signatures, and "zero values" that
11881212// represent the new variables. We also use this information to construct the if statement that
11891213// is inserted below the call to the extracted function.
1190- func generateReturnInfo (enclosing * ast.FuncType , pkg * types.Package , path []ast.Node , file * ast.File , info * types.Info , pos token.Pos , hasNonNestedReturns bool ) ([]* returnVariable , * ast.IfStmt , error ) {
1214+ func generateReturnInfo (enclosing * ast.FuncType , pkg * types.Package , path []ast.Node , file * ast.File , info * types.Info , start , end token.Pos , hasNonNestedReturns bool ) ([]* returnVariable , * ast.IfStmt , error ) {
11911215 var retVars []* returnVariable
11921216 var cond * ast.Ident
11931217 if ! hasNonNestedReturns {
11941218 // Generate information for the added bool value.
1195- name , _ := generateAvailableIdentifier ( pos , path , pkg , info , "shouldReturn" , 0 )
1219+ name , _ := generateNameOutsideOfRange ( start , end , path , pkg , info , "shouldReturn" , 0 )
11961220 cond = & ast.Ident {Name : name }
11971221 retVars = append (retVars , & returnVariable {
11981222 name : cond ,
@@ -1202,7 +1226,7 @@ func generateReturnInfo(enclosing *ast.FuncType, pkg *types.Package, path []ast.
12021226 }
12031227 // Generate information for the values in the return signature of the enclosing function.
12041228 if enclosing .Results != nil {
1205- idx := 0
1229+ nameIdx := make ( map [ string ] int ) // last integral suffixes of generated names
12061230 for _ , field := range enclosing .Results .List {
12071231 typ := info .TypeOf (field .Type )
12081232 if typ == nil {
@@ -1213,17 +1237,32 @@ func generateReturnInfo(enclosing *ast.FuncType, pkg *types.Package, path []ast.
12131237 if expr == nil {
12141238 return nil , nil , fmt .Errorf ("nil AST expression" )
12151239 }
1216- var name string
1217- name , idx = generateAvailableIdentifier (pos , path , pkg , info , "returnValue" , idx )
1218- z := analysisinternal .ZeroValue (file , pkg , typ )
1219- if z == nil {
1220- return nil , nil , fmt .Errorf ("can't generate zero value for %T" , typ )
1240+ names := []string {"" }
1241+ if len (field .Names ) > 0 {
1242+ names = nil
1243+ for _ , n := range field .Names {
1244+ names = append (names , n .Name )
1245+ }
1246+ }
1247+ for _ , name := range names {
1248+ bestName := "result"
1249+ if name != "" && name != "_" {
1250+ bestName = name
1251+ } else if n , ok := varNameForType (typ ); ok {
1252+ bestName = n
1253+ }
1254+ retName , idx := generateNameOutsideOfRange (start , end , path , pkg , info , bestName , nameIdx [bestName ])
1255+ nameIdx [bestName ] = idx
1256+ z := analysisinternal .ZeroValue (file , pkg , typ )
1257+ if z == nil {
1258+ return nil , nil , fmt .Errorf ("can't generate zero value for %T" , typ )
1259+ }
1260+ retVars = append (retVars , & returnVariable {
1261+ name : ast .NewIdent (retName ),
1262+ decl : & ast.Field {Type : expr },
1263+ zeroVal : z ,
1264+ })
12211265 }
1222- retVars = append (retVars , & returnVariable {
1223- name : ast .NewIdent (name ),
1224- decl : & ast.Field {Type : expr },
1225- zeroVal : z ,
1226- })
12271266 }
12281267 }
12291268 var ifReturn * ast.IfStmt
@@ -1240,6 +1279,48 @@ func generateReturnInfo(enclosing *ast.FuncType, pkg *types.Package, path []ast.
12401279 return retVars , ifReturn , nil
12411280}
12421281
1282+ type objKey struct { pkg , name string }
1283+
1284+ // conventionalVarNames specifies conventional names for variables with various
1285+ // standard library types.
1286+ //
1287+ // Keep this up to date with completion.conventionalAcronyms.
1288+ //
1289+ // TODO(rfindley): consider factoring out a "conventions" library.
1290+ var conventionalVarNames = map [objKey ]string {
1291+ {"" , "error" }: "err" ,
1292+ {"context" , "Context" }: "ctx" ,
1293+ {"sql" , "Tx" }: "tx" ,
1294+ {"http" , "ResponseWriter" }: "rw" , // Note: same as [AbbreviateVarName].
1295+ }
1296+
1297+ // varNameForTypeName chooses a "good" name for a variable with the given type,
1298+ // if possible. Otherwise, it returns "", false.
1299+ //
1300+ // For special types, it uses known conventional names.
1301+ func varNameForType (t types.Type ) (string , bool ) {
1302+ var typeName string
1303+ if tn , ok := t .(interface { Obj () * types.TypeName }); ok {
1304+ obj := tn .Obj ()
1305+ k := objKey {name : obj .Name ()}
1306+ if obj .Pkg () != nil {
1307+ k .pkg = obj .Pkg ().Name ()
1308+ }
1309+ if name , ok := conventionalVarNames [k ]; ok {
1310+ return name , true
1311+ }
1312+ typeName = obj .Name ()
1313+ } else if b , ok := t .(* types.Basic ); ok {
1314+ typeName = b .Name ()
1315+ }
1316+
1317+ if typeName == "" {
1318+ return "" , false
1319+ }
1320+
1321+ return AbbreviateVarName (typeName ), true
1322+ }
1323+
12431324// adjustReturnStatements adds "zero values" of the given types to each return statement
12441325// in the given AST node.
12451326func adjustReturnStatements (returnTypes []* ast.Field , seenVars map [types.Object ]ast.Expr , file * ast.File , pkg * types.Package , extractedBlock * ast.BlockStmt ) error {
@@ -1346,9 +1427,8 @@ func initializeVars(uninitialized []types.Object, retVars []*returnVariable, see
13461427 // Each variable added from a return statement in the selection
13471428 // must be initialized.
13481429 for i , retVar := range retVars {
1349- n := retVar .name .(* ast.Ident )
13501430 valSpec := & ast.ValueSpec {
1351- Names : []* ast.Ident {n },
1431+ Names : []* ast.Ident {retVar . name },
13521432 Type : retVars [i ].decl .Type ,
13531433 }
13541434 genDecl := & ast.GenDecl {
0 commit comments