blob: 9c766e34c11f7b11edc46b8584535099743bf597 [file] [log] [blame]
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fix
import (
"go/token"
"go/types"
"github.com/dave/dst"
)
// assignPre rewrites assignments. This function is executed by traversing the tree in preorder.
// Assignments with operations are handled by assignOpPre.
func assignPre(c *cursor) bool {
stmt, ok := c.Node().(*dst.AssignStmt)
if !ok {
c.Logf("ignoring %T (looking for AssignStmt)", c.Node())
return true
}
if stmt.Tok != token.ASSIGN {
c.Logf("ignoring AssignStmt with Tok %v (looking for ASSIGN)", stmt.Tok)
return true
}
// Not handled: shallow copy support.
if len(stmt.Lhs) != len(stmt.Rhs) {
c.Logf("ignoring AssignStmt with len(lhs)=%d != len(rhs)=%d", len(stmt.Lhs), len(stmt.Rhs))
// Not handled: assignments where len(lhs) != len(rhs):
// - calls
// - chan ops
// - map Access
// - type assertions
return true
}
// Handle the most common case: single assignment.
if len(stmt.Lhs) == 1 {
c.Logf("len(lhs) = 1")
lhs, rhs := stmt.Lhs[0], stmt.Rhs[0]
// *m.F = v => m.SetF(v)
if star, ok := lhs.(*dst.StarExpr); ok {
c.Logf("lhs is a StarExpr")
sel, ok := c.trackedProtoFieldSelector(star.X)
if !ok {
c.Logf("ignoring: lhs is not a proto field selector")
return true
}
c.Replace(c.expr2stmt(sel2call(c, "Set", sel, rhs, *stmt.Decorations()), sel))
c.Logf("rewriting AssignStmt")
return true
}
// Handle "m.F = v"
field, ok := c.protoFieldSelector(lhs)
if !ok {
c.Logf("ignoring: lhs is not a proto field selector")
return true
}
// Check if either side is a proto field selector in -types_to_update
// and update the whole assignment.
_, lhsOk := c.trackedProtoFieldSelector(lhs)
_, rhsOk := c.trackedProtoFieldSelector(rhs)
if !lhsOk && !rhsOk {
c.Logf("ignoring: neither lhs nor rhs are (tracked) proto field selectors")
return true
}
c.Logf("attempting to rewrite...")
if a, ok := rewriteFieldAssign(c, field, rhs, *stmt.Decorations()); ok {
c.Logf("...success")
c.Replace(a)
} else {
c.Logf("...failure")
}
return true
}
// Rewriting multi-assignment may change order of evaluation. Hence this is a yellow rewrite.
if c.lvl.le(Green) {
return true
}
if err := assignmentIsSwap(c, stmt); err == nil && !c.lvl.ge(Yellow) {
// Swaps are yellow rewrites because getPost only handles them in the yellow level.
return true
} else if err != nil {
c.Logf("%s", err.Error())
}
// Multi-assignment in simple statements is handled by assignPost. It requires updating
// grandparent (calling parent.InsertBefore) of the rewritten node and the dstutil.Cursor API
// doesn't provide a way to do that. Here, we only update statements in blocks because we can
// call c.InsertBefore.
if _, ok := c.Cursor.Parent().(*dst.BlockStmt); !ok {
c.Logf("ignoring: multi-assignment outside of BlockStmt (handled by assignPost)")
return true
}
// Don't change the multi-assignment structure if there are no proto-related
// rewrites in there.
var usesProtos bool
for _, lhs := range stmt.Lhs {
if star, ok := lhs.(*dst.StarExpr); ok {
if _, ok := c.trackedProtoFieldSelector(star.X); ok {
usesProtos = true
break
}
}
if _, ok := c.trackedProtoFieldSelector(lhs); ok {
usesProtos = true
break
}
}
if !usesProtos {
c.Logf("ignoring AssignStmt without protos")
return true
}
var decs dst.NodeDecs
lastIdx := len(stmt.Lhs) - 1
for i, lhs := range stmt.Lhs {
switch i {
case 0:
decs = dst.NodeDecs{
Before: stmt.Decorations().Before,
Start: stmt.Decorations().Start,
}
case lastIdx:
decs = dst.NodeDecs{
After: stmt.Decorations().After,
End: stmt.Decorations().End,
}
default:
decs = dst.NodeDecs{}
}
rhs := stmt.Rhs[i]
// rewrite "*m.F = v"
if star, ok := lhs.(*dst.StarExpr); ok {
if sel, ok := c.trackedProtoFieldSelector(star.X); ok {
c.Logf("rewriting %v = %v", sel.Sel.Name, rhs)
c.InsertBefore(c.expr2stmt(sel2call(c, "Set", sel, rhs, *stmt.Decorations()), sel))
} else {
c.Logf("ignoring: lhs is not a proto field selector")
}
continue
}
// rewrite "m.F = v"
if field, ok := c.trackedProtoFieldSelector(lhs); ok {
c.Logf("lhs %d is a proto field selector", i)
if a, ok := rewriteFieldAssign(c, field, rhs, decs); ok {
c.Logf("rewriting %v = %v", field.Sel.Name, rhs)
c.InsertBefore(a)
continue
}
}
as := &dst.AssignStmt{
Lhs: []dst.Expr{lhs},
Tok: token.ASSIGN,
Rhs: []dst.Expr{rhs},
}
as.Decs.NodeDecs = decs
c.Logf("rewriting %v = %v", lhs, rhs)
c.InsertBefore(as)
}
c.Delete()
return true
}
// assignPost rewrites multi-assignments in simple statements. This function is executed by
// traversing the tree in postorder.
func assignPost(c *cursor) bool {
// Splitting multi-assignments is a yellow rewrite because it changes order of evaluation.
if c.lvl.le(Green) {
return true
}
// If either expression a or b is a proto direct field access, rewrite:
//
// if a, b = f(), g(); cond { // Similar for init statements in "for" and "switch".
// =>
// a = f()
// b = g()
// if cond {
//
// and apply single-assignment rewrite rules for individual assign statements.
var initStmt *dst.AssignStmt
n := c.Node()
switch n := n.(type) {
case *dst.IfStmt:
if isMultiProtoAssign(c, n.Init) {
initStmt = n.Init.(*dst.AssignStmt)
n.Init = nil
}
case *dst.ForStmt:
if isMultiProtoAssign(c, n.Init) {
initStmt = n.Init.(*dst.AssignStmt)
n.Init = nil
}
case *dst.SwitchStmt:
if isMultiProtoAssign(c, n.Init) {
initStmt = n.Init.(*dst.AssignStmt)
n.Init = nil
}
case *dst.TypeSwitchStmt:
if isMultiProtoAssign(c, n.Init) {
initStmt = n.Init.(*dst.AssignStmt)
n.Init = nil
}
}
if initStmt == nil {
return true
}
for i, lhs := range initStmt.Lhs {
rhs := initStmt.Rhs[i]
if a, ok := rewriteFieldAssign(c, lhs, rhs, dst.NodeDecs{}); ok {
c.InsertBefore(a)
continue
}
c.InsertBefore(&dst.AssignStmt{
Lhs: []dst.Expr{lhs},
Tok: token.ASSIGN,
Rhs: []dst.Expr{rhs},
})
}
c.numUnsafeRewritesByReason[EvalOrderChange]++
return true
}
// assignOpPre rewrites assignment operations (x op= y).
// This function is executed by traversing the tree in preorder.
func assignOpPre(c *cursor) bool {
stmt, ok := c.Node().(*dst.AssignStmt)
if !ok {
return true
}
if stmt.Tok == token.ASSIGN || stmt.Tok == token.DEFINE {
return false
}
// Assignment operations must have exactly one lhs and one rhs value,
// see https://go.dev/ref/spec#Assignment_statements.
lhs, rhs := stmt.Lhs[0], stmt.Rhs[0]
if star, ok := lhs.(*dst.StarExpr); ok {
lhs = star.X
}
sel, ok := c.trackedProtoFieldSelector(lhs)
if !ok {
return false
}
tok := stmt.Tok
switch stmt.Tok {
case token.ADD_ASSIGN:
tok = token.ADD
case token.SUB_ASSIGN:
tok = token.SUB
case token.MUL_ASSIGN:
tok = token.MUL
case token.QUO_ASSIGN:
tok = token.QUO
case token.REM_ASSIGN:
tok = token.REM
case token.AND_ASSIGN:
tok = token.AND
case token.OR_ASSIGN:
tok = token.OR
case token.XOR_ASSIGN:
tok = token.XOR
case token.SHL_ASSIGN:
tok = token.SHL
case token.SHR_ASSIGN:
tok = token.SHR
case token.AND_NOT_ASSIGN:
tok = token.AND_NOT
default:
c.Logf("unexpected token: %v", stmt.Tok)
return false
}
binExpr := &dst.BinaryExpr{
X: sel2call(c, "Get", sel, nil, dst.NodeDecs{}),
Op: tok,
Y: rhs,
}
c.setType(binExpr, c.typeOf(lhs))
startEndDec := dst.NodeDecs{
Start: stmt.Decorations().Start,
End: stmt.Decorations().End,
}
selClone := cloneSelectorExpr(c, sel)
c.Replace(c.expr2stmt(sel2call(c, "Set", selClone, binExpr, startEndDec), selClone))
return false
}
func isNeverNilSliceExpr(c *cursor, e dst.Expr) bool {
// Is this a string to byte conversion?
if ce, ok := e.(*dst.CallExpr); ok && len(ce.Args) == 1 {
if bt, ok := c.typeOf(ce.Args[0]).(*types.Basic); ok && bt.Kind() == types.String {
if at, ok := ce.Fun.(*dst.ArrayType); ok {
if id, ok := at.Elt.(*dst.Ident); ok && id.Name == "byte" {
return true
}
}
}
}
if _, ok := e.(*dst.CompositeLit); ok {
if _, ok := c.typeOf(e).(*types.Slice); ok {
return true
}
}
se, ok := e.(*dst.SliceExpr)
if !ok {
return false
}
if bl, ok := se.Low.(*dst.BasicLit); ok && bl.Value != "0" {
return true
}
if bl, ok := se.High.(*dst.BasicLit); ok && bl.Value != "0" {
return true
}
return false
}
// rewriteFieldAssign rewrites a direct field assignment
//
// lhs = rhs where lhs is "m.F"
//
// to a form that works in the opaque proto API world.
func rewriteFieldAssign(c *cursor, lhs, rhs dst.Expr, decs dst.NodeDecs) (dst.Stmt, bool) {
lhsSel, ok := c.protoFieldSelector(lhs)
if !ok {
c.Logf("ignoring: lhs is not a proto field selector")
return nil, false
}
// Drop parens around rhs, if any. Rhs becomes an argument to a function call. It's never
// necessary to keep it in parenthesis. One situation where rhs is a ParenExpr happens when it
// used to be a composite literal in a simple statement. For example:
//
// if _, M.F = nil, (&pb.M{}); {
if pe, ok := rhs.(*dst.ParenExpr); ok {
rhs = pe.X
}
// m.F = proto.{String, Int, ...}(V) => m.SetF(V)
if arg, ok := protoHelperCall(c, rhs); ok {
c.Logf("rewriting proto helper call")
return c.expr2stmt(sel2call(c, "Set", lhsSel, arg, decs), lhsSel), true
}
// m.F = pb.EnumValue.Enum() => m.SetF(pb.EnumValue)
if enumVal, ok := enumHelperCall(c, rhs); ok {
c.Logf("rewriting Enum() call")
if t := c.typeOfOrNil(enumVal); t != nil {
if pt, ok := t.(*types.Pointer); ok {
enumVal = &dst.StarExpr{X: enumVal}
c.setType(enumVal, pt.Elem())
}
}
return c.expr2stmt(sel2call(c, "Set", lhsSel, enumVal, decs), lhsSel), true
}
// m.F = new(MsgType) => m.SetF(new(MsgType))
// m.F = new(BasicType) => m.SetF(ZeroValueOf(BasicType))
// m.F = new(EnumType) => m.SetF(EnumType(0))
if arg, ok := newConstructorCall(c, rhs); ok {
c.Logf("rewriting constructor")
return c.expr2stmt(sel2call(c, "Set", lhsSel, arg, decs), lhsSel), true
}
// m.F = nil => m.ClearF()
if ident, ok := rhs.(*dst.Ident); ok && ident.Name == "nil" {
c.Logf("rewriting nil assignment...")
if c.useClearOrHas(lhsSel) {
c.Logf("...with Clear()")
return c.expr2stmt(sel2call(c, "Clear", lhsSel, nil, decs), lhsSel), true
}
c.Logf("...with Set()")
return c.expr2stmt(sel2call(c, "Set", lhsSel, rhs, decs), lhsSel), true
}
// This condition is intentionally placed after the ClearF condition just
// above so that we do not need to handle the nil assignment case.
//
// m.F = ptrToEnum
// =>
// if ptrToEnum != nil {
// m1.SetF(*ptrToEnum)
// } else {
// m1.ClearF()
// }
if t := c.typeOfOrNil(lhsSel); t != nil && isPtr(t) {
et := t.Underlying().(*types.Pointer).Elem()
_, fieldCopy := c.trackedProtoFieldSelector(rhs)
c.Logf("isEnum(%v) = %v, fieldCopy = %v", et, isEnum(et), fieldCopy)
if !fieldCopy && isEnum(et) {
// special case for m.E = &eVal => m.SetE(eVal)
if ue, ok := rhs.(*dst.UnaryExpr); ok && ue.Op == token.AND {
stmt := c.expr2stmt(sel2call(c, "Set", lhsSel, ue.X, decs), lhsSel)
return stmt, true
}
lhs2 := cloneSelectorExpr(c, lhsSel)
ifStmt, v := ifNonNil(c, lhsSel, rhs, decs)
ifStmt.Body.List = []dst.Stmt{
c.expr2stmt(sel2call(c, "Set", lhs2, deref(c, cloneExpr(c, v)), dst.NodeDecs{}), lhs2),
}
return ifStmt, true
}
}
// m.F = []byte(v) => m.SetF([]byte(v))
// m.F = []byte("...") => m.SetF([]byte("..."))
if isBytesConversion(c, rhs) {
c.Logf("rewriting []byte conversion")
return c.expr2stmt(sel2call(c, "Set", lhsSel, rhs, decs), lhsSel), true
}
// For proto2 bytes field, rewrite statement
// m.F = Expr
// =>
// if x := Expr; x != nil {
// m.SetF(x)
// }
//
// Or, if we cannot determine whether Clear() is safe to omit:
//
// if x := Expr; x != nil {
// m.SetF(x)
// } else {
// m.ClearF()
// }
//
// Can only rewrite for statements and not for expressions.
if _, ok := c.Parent().(*dst.BlockStmt); ok {
c.Logf("rewriting assignment with rhs Expr")
f := c.objectOf(lhsSel.Sel).(*types.Var)
isProto2 := !isProto3Field(c.typeOf(lhsSel.X), f.Name())
if slice, ok := f.Type().(*types.Slice); ok && isProto2 {
if basic, ok := slice.Elem().(*types.Basic); ok && basic.Kind() == types.Uint8 {
if isNeverNilSliceExpr(c, rhs) {
stmt := c.expr2stmt(sel2call(c, "Set", lhsSel, rhs, decs), lhsSel)
return stmt, true
}
// Duplicate LHS so that it can be used in both "then" and "else" blocks.
// It's ok, regardless of the exact details of LHS, because:
// - before this change, LHS was evaluated exactly once, and
// - after this change, LHS is still evaluated exactly once
lhsSelClone := cloneSelectorExpr(c, lhsSel)
ifStmt, v := ifNonNil(c, lhsSel, rhs, decs)
ifStmt.Body.List = []dst.Stmt{
c.expr2stmt(sel2call(c, "Set", lhsSelClone, cloneExpr(c, v), dst.NodeDecs{}), lhsSelClone),
}
return ifStmt, true
}
}
}
// m.Oneof = &pb.M_Oneof{K: V} => m.SetK(V)
// m.Oneof = &pb.M_Oneof{V} => m.SetK(V)
// m.Oneof = &pb.M_Oneof{} => m.SetK(ZeroValueOf(BasicType))
//
// Where K is the field name of the sole field in M_Oneof.
if isOneof(c.typeOf(lhsSel.Sel)) {
c.Logf("rewriting oneof wrapper")
name, typ, val, oneofDecs, ok := destructureOneofWrapper(c, rhs)
if !ok {
c.Logf("ignoring: destructuring oneof wrapper failed")
if c.lvl.ge(Red) {
c.numUnsafeRewritesByReason[OneofFieldAccess]++
addCommentAbove(c.Node(), lhsSel.X, "// DO NOT SUBMIT: Migrate the direct oneof field access (go/go-opaque-special-cases/oneof.md).")
}
return nil, false
}
if val == nil {
if !isScalar(typ) {
c.Logf("...failed because lhs is not a scalar type")
return nil, false
}
val = scalarTypeZeroExpr(c, typ)
}
lhsSel.Sel.Name = name
c.Logf("...success")
if oneofDecs != nil {
decs.Start = append(decs.Start, (*oneofDecs).Start...)
decs.End = append(decs.End, (*oneofDecs).End...)
}
return c.expr2stmt(sel2call(c, "Set", lhsSel, val, decs), lhsSel), true
}
// m1.F = m2.F
//
// [proto2]
// if m2.HasF() {
// m1.SetF(m2.GetF())
// } else {
// m1.ClearF()
// }
// (or variations based on existence of side effects when evaluating m1 or m2)
//
// [proto3]
// m1.SetF(m2.GetF())
isProto2 := isPtrToBasic(c.underlyingTypeOf(lhsSel)) // Bytes fields don't behave like other proto2 scalars.
if rhsSel, ok := c.trackedProtoFieldSelector(rhs); ok {
c.Logf("rewriting proto field to proto field assignment")
if !isProto2 {
return c.expr2stmt(sel2call(c, "Set", lhsSel, sel2call(c, "Get", rhsSel, nil, dst.NodeDecs{}), decs), lhsSel), true
}
// If RHS has no side effects then just evaluate it inline. However, if it
// is not known to be side-effect free then evaluate it once, in the init
// statement.
var rhs1 *dst.SelectorExpr // We need two copies of rhs. They are identical.
var initStmt dst.Stmt
if c.isSideEffectFree(rhsSel) {
rhs1 = rhsSel
} else {
v := &dst.Ident{Name: "x"}
c.setType(v, c.typeOf(rhsSel.X))
initStmt = &dst.AssignStmt{
Lhs: []dst.Expr{v},
Tok: token.DEFINE,
Rhs: []dst.Expr{rhsSel.X},
}
rhs1 = &dst.SelectorExpr{
X: &dst.Ident{Name: v.Name},
Sel: rhsSel.Sel,
}
c.setType(rhs1, c.typeOf(rhsSel))
c.setType(rhs1.X, c.typeOf(v))
}
rhs2 := cloneSelectorExpr(c, rhs1)
lhs1, lhs2 := lhsSel, cloneSelectorExpr(c, lhsSel) // We need two copies of LHS. They are identical.
var elseStmt dst.Stmt
if clearNeeded(c, lhsSel) {
c.Logf("Clear() statement is needed")
elseStmt = c.expr2stmt(sel2call(c, "Clear", lhs1, nil, dst.NodeDecs{}), lhs1)
}
// Move end-of-line comments to above the if conditional.
if len(decs.End) > 0 {
decs.Start = append(decs.Start, decs.End...)
decs.End = nil
}
var stmt dst.Stmt = c.expr2stmt(sel2call(c, "Set", lhs2, sel2call(c, "Get", rhs2, nil, dst.NodeDecs{}), dst.NodeDecs{}), lhs2)
if hasNeeded(c, rhsSel) {
stmt = &dst.IfStmt{
Init: initStmt,
Cond: sel2call(c, "Has", rhs1, nil, dst.NodeDecs{}),
Body: &dst.BlockStmt{
List: []dst.Stmt{
stmt,
},
},
Else: elseStmt,
Decs: dst.IfStmtDecorations{NodeDecs: decs},
}
}
return stmt, true
}
// m.F = V => m.SetF(V)
if !isProto2 {
c.Logf("rewriting direct field access to setter")
return c.expr2stmt(sel2call(c, "Set", lhsSel, rhs, decs), lhsSel), true
}
// m.F = &V => m.SetF(V)
if isAddr(rhs) {
c.Logf("rewriting direct field access to setter (rhs &Expr)")
return c.expr2stmt(sel2call(c, "Set", lhsSel, deref(c, rhs), decs), lhsSel), true
}
// m.F = V => m.SetF(*V) (red rewrite: loses aliasing)
if c.lvl.ge(Red) {
c.Logf("rewriting direct field access to setter (losing pointer aliasing)")
lhs2 := cloneSelectorExpr(c, lhsSel)
ifStmt, v := ifNonNil(c, lhsSel, rhs, decs)
ifStmt.Body.List = []dst.Stmt{
c.expr2stmt(sel2call(c, "Set", lhs2, deref(c, cloneExpr(c, v)), dst.NodeDecs{}), lhs2),
}
c.numUnsafeRewritesByReason[PointerAlias]++
return ifStmt, true
}
c.Logf("no applicable rewrite found")
return nil, false
}
// clearNeeded figures out if for the specified SelectorExpr, a Clear() call
// needs to be inserted before or not.
//
// clearNeeded looks at the AST of the current scope, considering all statements
// between the initial assignment and the SelectorExpr:
//
// … // (not checked yet)
// mm2 := &mypb.Message{} // initial assignment
// mm2.SetBytes([]byte("hello world")) // checked by clearNeeded()
// … // checked by clearNeeded()
// mm2.I32 = 23 // SelectorExpr
// proto.Merge(mm2, src) // (not checked anymore)
// …
func clearNeeded(c *cursor, sel *dst.SelectorExpr) bool {
// sel is something like dst.SelectorExpr{
// X: &dst.Ident{"mm2"},
// Sel: &dst.Ident{"I32"},
// }
if _, ok := sel.X.(*dst.Ident); !ok {
return true
}
innerMost, _ := c.enclosingASTStmt(sel.X)
if innerMost == nil {
return true
}
enclosing, ok := c.typesInfo.dstMap[innerMost]
if !ok {
c.Logf("BUG: no corresponding dave/dst node for go/ast node %T / %+v", innerMost, innerMost)
return true
}
first := compositLiteralInitialer(enclosing, sel.X.(*dst.Ident).Name)
if first == nil {
// The variable we are looking for is not initialized
// unconditionally in this scope.
return true
}
firstSeen := false
lastSeen := false
usageFound := false
var visit visitorFunc
xObj := c.objectOf(sel.X.(*dst.Ident))
selObj := c.objectOf(sel.Sel)
selName := fixConflictingNames(c.typeOf(sel.X), "Set", sel.Sel.Name)
visit = func(n dst.Node) dst.Visitor {
if n == first {
firstSeen = true
// Don't check the children of the definition because
// it would look like a usage.
return nil
}
if !firstSeen {
// As long as we have not seen the first node we don't
// need to look at any of the statements because they
// cannot influence first.
return visit
}
if lastSeen {
return nil
}
if as, ok := n.(*dst.AssignStmt); ok {
for _, lhs := range as.Lhs {
if lhs == sel {
lastSeen = true
// Skip recursing into children; all subsequent visit() calls
// will return immediately.
return nil
}
// Is the field assigned to?
if usesObject(c, lhs, xObj) && usesObject(c, lhs, selObj) {
usageFound = true
return nil
}
}
}
// Access is okay if it's not a setter for the field
// and if it is not assigned to (checked above).
if doesNotModifyField(c, n, selName) {
return nil
}
if id, ok := n.(*dst.Ident); ok && id.Name == sel.X.(*dst.Ident).Name {
c.Logf("found non-proto-field-selector usage of %q", id.Name)
usageFound = true
}
return visit // recurse into children
}
dst.Walk(visit, enclosing)
// Clear() calls are definitely needed when:
//
// 1. !firstSeen — we couldn’t find the declaration of <sel> in the
// innermost scope.
//
// 2. or !lastSeen — we couldn’t find the usage of <sel> (bug?)
//
// 3. or usageFound — we did find a usage that we didn’t expect.
return !firstSeen || !lastSeen || usageFound
}
// isMultiProtoAssign returns true if stmt is a multi-assignment that assigns at least one protocol
// buffer field. Note that irregular assignments (e.g. "a,b := m[c]") are not considered to be
// multi-assignments.
func isMultiProtoAssign(c *cursor, stmt dst.Stmt) bool {
a, ok := stmt.(*dst.AssignStmt)
if !ok || len(a.Lhs) < 2 || len(a.Lhs) != len(a.Rhs) {
return false
}
for _, lhs := range a.Lhs {
if _, ok := c.trackedProtoFieldSelector(lhs); ok {
return true
}
}
return false
}
// isBytesConversion returns true if x is an explicit conversion to a slice of
// bytes. That is, when x has the form "[]byte(...)".
func isBytesConversion(c *cursor, x dst.Expr) bool {
call, ok := x.(*dst.CallExpr)
if !ok {
return false
}
fun, ok := call.Fun.(*dst.ArrayType)
if !ok {
return false
}
ident, ok := fun.Elt.(*dst.Ident)
if !ok {
return false
}
return c.objectOf(ident) == types.Universe.Lookup("byte")
}
// newConstructorCall returns true if x is a 'new' call. It also returns an
// argument that should be provided to a corresponding proto setter if the new
// call was to be replaced by a set call.
func newConstructorCall(c *cursor, expr dst.Expr) (dst.Expr, bool) {
call, ok := expr.(*dst.CallExpr)
if !ok || len(call.Args) != 1 {
return nil, false
}
ident, ok := call.Fun.(*dst.Ident)
if !ok {
return nil, false
}
if c.objectOf(ident) != types.Universe.Lookup("new") {
return nil, false
}
t := c.typeOf(call.Args[0])
if t, ok := t.(*types.Basic); ok {
return scalarTypeZeroExpr(c, t), true
}
if _, ok := t.(*types.Named); !ok {
return nil, false
}
// Message
if _, ok := t.Underlying().(*types.Struct); ok {
return call, true // new(M) is fine
}
// Enum
if !isBasic(t.Underlying()) {
return nil, false
}
zero := &dst.Ident{Name: "0"}
c.setType(zero, types.Typ[types.UntypedInt])
conv := &dst.CallExpr{
Fun: call.Args[0],
Args: []dst.Expr{zero},
}
c.setType(conv, t)
return conv, true
}
// enumHelperCall returns true if expr is a enum helper call (e.g.
// "pb.MyMessage_MyEnumVal.Enum()"). If so, it also returns the enum value
// ("MyEnumVal" in the previous example).
func enumHelperCall(c *cursor, expr dst.Expr) (dst.Expr, bool) {
call, ok := expr.(*dst.CallExpr)
if !ok {
return nil, false
}
sel, ok := call.Fun.(*dst.SelectorExpr)
if !ok {
return nil, false
}
if sel.Sel.Name != "Enum" {
return nil, false
}
var res dst.Expr = sel.X
// It is possible to use methods enums as if they were free functions by
// passing the receiver as first argument. We know that the Enum method
// does not have any parameters. This means if it is called with an argument
// it must be used as free function and the argument is the receiver, e.g.:
// (e.g. "pb.MyMessage_MyEnum.Enum(pb.MyMessage_MyEnumVal)").
//
// Note: we cannot use the type system to determine whether the free function
// or method is used because these are two are the same from the type
// systems's point of view.
if len(call.Args) == 1 {
res = call.Args[0]
}
return res, true
}
// isSelectorExprWithIdent return true if e is a simple selector expression where
// X is of type *dst.Ident.
func isSelectorExprWithIdent(e dst.Expr) bool {
rhsSel, ok := e.(*dst.SelectorExpr)
if !ok {
return false
}
if _, ok := rhsSel.X.(*dst.Ident); !ok {
return false
}
return true
}
// ifNonNil returns a *dst.IfStmt that checks if rhs is not nil (if rhs is nil,
// Clear() is called). If needed, a temporary variable (x) is introduced to only
// evaluate rhs once. The returned *dst.Ident refers either to the temporary
// variable (if needed) or to rhs. If possible, the Clear() call will be elided.
func ifNonNil(c *cursor, lhsSel *dst.SelectorExpr, rhs dst.Expr, decs dst.NodeDecs) (*dst.IfStmt, dst.Expr) {
var elseStmt dst.Stmt
if clearNeeded(c, lhsSel) {
c.Logf("Clear() statement is needed")
elseStmt = c.expr2stmt(sel2call(c, "Clear", lhsSel, nil, dst.NodeDecs{}), lhsSel)
}
var v dst.Expr
var initAssign dst.Stmt
if rhsIdent, ok := rhs.(*dst.Ident); ok {
// If RHS is already an identifier, we skip generating an
// initializer in the if statement (x := rhs) and just use
// the RHS directly.
v = rhsIdent
} else if isSelectorExprWithIdent(rhs) {
v = rhs
} else {
v = &dst.Ident{Name: "x"}
c.setType(v, c.typeOf(rhs))
initAssign = &dst.AssignStmt{
Lhs: []dst.Expr{cloneIdent(c, v.(*dst.Ident))},
Tok: token.DEFINE,
Rhs: []dst.Expr{rhs},
}
}
untypedNil := &dst.Ident{Name: "nil"}
c.setType(untypedNil, types.Typ[types.UntypedNil])
cond := &dst.BinaryExpr{
X: v,
Op: token.NEQ,
Y: untypedNil,
}
c.setType(cond, types.Typ[types.Bool])
// Move end-of-line comments to above the if conditional.
if len(decs.End) > 0 {
decs.Start = append(decs.Start, decs.End...)
decs.End = nil
}
return &dst.IfStmt{
Init: initAssign,
Cond: cond,
Body: &dst.BlockStmt{},
Else: elseStmt,
Decs: dst.IfStmtDecorations{NodeDecs: decs},
}, v
}
// doesNotModifyField returns true if n does not modify a proto field named name.
func doesNotModifyField(c *cursor, n dst.Node, name string) bool {
if selFun, sig, ok := c.protoFieldSelectorOrAccessor(n); ok {
if sig == nil || selFun.Sel.Name != "Set"+name {
// Skip recursing into children: proto field selector usages are
// okay; we could still skip the Clear() methods.
return true
}
}
return false
}
// compositLiteralInitialer return the node that is a direct child of enclosing
// and initialized a variabled named `name` unconditionally with a composite
// literal. Returns nil if there is no such node.
func compositLiteralInitialer(enclosing dst.Node, name string) dst.Node {
for _, n := range directChildren(enclosing) {
as, ok := n.(*dst.AssignStmt)
if !ok {
continue
}
if len(as.Lhs) != len(as.Rhs) {
continue
}
for i, lhs := range as.Lhs {
if id, ok := lhs.(*dst.Ident); ok && id.Name == name {
// We consider everything but composite literal
// initialization as unknown and thus unsafe.
if _, ok := isCompositeLit(as.Rhs[i], as); ok {
return n
}
}
}
}
return nil
}
func usesObject(c *cursor, expr dst.Expr, obj types.Object) bool {
found := false
var visit visitorFunc
visit = func(n dst.Node) dst.Visitor {
id, ok := n.(*dst.Ident)
if !ok {
return visit
}
if c.objectOf(id) == obj {
found = true
return nil
}
return visit
}
dst.Walk(visit, expr)
return found
}
// directChildren returns a list of dst.Stmts if the n is a node opens a scope.
func directChildren(n dst.Node) []dst.Stmt {
switch t := n.(type) {
case *dst.BlockStmt:
return t.List
case *dst.CaseClause:
return t.Body
case *dst.CommClause:
return t.Body
}
return nil
}
func deref(c *cursor, expr dst.Expr) dst.Expr {
ue, ok := expr.(*dst.UnaryExpr)
if ok {
return ue.X
}
out := &dst.StarExpr{X: expr}
c.setType(out, c.underlyingTypeOf(expr).(*types.Pointer).Elem())
return out
}
func stringIn(s string, ss []string) bool {
for _, v := range ss {
if s == v {
return true
}
}
return false
}
// protoHelperCall returns true if expr is a proto helper call (e.g. "proto.String(s)"). If so, it
// also returns argument to the helper.
func protoHelperCall(c *cursor, expr dst.Expr) (dst.Expr, bool) {
call, ok := expr.(*dst.CallExpr)
if !ok {
return nil, false
}
sel, ok := call.Fun.(*dst.SelectorExpr)
if !ok {
return nil, false
}
if sel.Sel.Name == "Int" && !isBasicLit(call.Args[0]) {
// "m.F = proto.Int(v)" => "m.SetF(int32(v))"
x := &dst.CallExpr{
Fun: dst.NewIdent("int32"),
Args: []dst.Expr{call.Args[0]},
}
c.setType(x, types.Universe.Lookup("int32").Type())
c.setType(x.Fun, types.Universe.Lookup("int32").Type())
return x, true
}
if !stringIn(sel.Sel.Name, []string{"Bool", "Float32", "Float64", "Int", "Int32", "Int64", "String", "Uint32", "Uint64"}) {
return nil, false
}
if c.objectOf(sel.Sel).Pkg().Path() != protoImport {
return nil, false
}
return call.Args[0], true
}
func isBasicLit(x dst.Expr) bool {
_, ok := x.(*dst.BasicLit)
return ok
}