Repository: google/go-flow-levee
Pull request #12 (2020-05-13 17:03:55)

Move the logic related to determining the name of a field into the utils package.
Move the logic related to determining the name of a field into the utils package.


Patch:
diff --git a/internal/levee.go b/internal/levee.go
index 6ab97c1..b0084e8 100644
--- a/internal/levee.go
+++ b/internal/levee.go
@@ -115,13 +115,8 @@ func (c config) isSource(t types.Type) bool {
 func (c config) isSourceFieldAddr(fa *ssa.FieldAddr) bool {
 	// fa.Type() refers to the accessed field's type.
 	// fa.X.Type() refers to the surrounding struct's type.
-
 	deref := utils.Dereference(fa.X.Type())
-	st, ok := deref.Underlying().(*types.Struct)
-	if !ok {
-		return false
-	}
-	fieldName := st.Field(fa.Field).Name()
+	fieldName := utils.FieldName(fa)
 
 	for _, p := range c.Sources {
 		if n, ok := deref.(*types.Named); ok &&
diff --git a/internal/pkg/utils/utils.go b/internal/pkg/utils/utils.go
index 8eeb53d..da67e87 100644
--- a/internal/pkg/utils/utils.go
+++ b/internal/pkg/utils/utils.go
@@ -15,7 +15,10 @@
 // Package utils contains various utility functions.
 package utils
 
-import "go/types"
+import (
+	"go/types"
+	"golang.org/x/tools/go/ssa"
+)
 
 // Dereference returns the underlying type of a pointer.
 // If the input is not a pointer, then the type of the input is returned.
@@ -28,3 +31,16 @@ func Dereference(t types.Type) types.Type {
 		t = tt.Elem()
 	}
 }
+
+// FieldName returns the name of the field identified by the FieldAddr.
+// It is the responsibility of the caller to ensure that the returned value is a non-empty string.
+func FieldName(fa *ssa.FieldAddr) string {
+	// fa.Type() refers to the accessed field's type.
+	// fa.X.Type() refers to the surrounding struct's type.
+	d := Dereference(fa.X.Type())
+	st, ok := d.Underlying().(*types.Struct)
+	if !ok {
+		return ""
+	}
+	return st.Field(fa.Field).Name()
+}


Test patch:
diff --git a/internal/pkg/utils/testdata/src/fieldname/embedded/test.go b/internal/pkg/utils/testdata/src/fieldname/embedded/test.go
new file mode 100644
index 0000000..021c348
--- /dev/null
+++ b/internal/pkg/utils/testdata/src/fieldname/embedded/test.go
@@ -0,0 +1,30 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package embedded
+
+import "fmt"
+
+type foo struct {
+	name string
+}
+
+type bar struct {
+	*foo
+}
+
+func f() {
+	bar := &bar{foo: &foo{}}
+	fmt.Printf("%v", bar)
+}
diff --git a/internal/pkg/utils/testdata/src/fieldname/regular/test.go b/internal/pkg/utils/testdata/src/fieldname/regular/test.go
new file mode 100644
index 0000000..3cbefa6
--- /dev/null
+++ b/internal/pkg/utils/testdata/src/fieldname/regular/test.go
@@ -0,0 +1,26 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package regular
+
+import "fmt"
+
+type foo struct {
+	name string
+}
+
+func f() {
+	bar := &foo{name: "n"}
+	fmt.Printf("%v", bar)
+}
diff --git a/internal/pkg/utils/utils_test.go b/internal/pkg/utils/utils_test.go
index a6a032b..5d8f0eb 100644
--- a/internal/pkg/utils/utils_test.go
+++ b/internal/pkg/utils/utils_test.go
@@ -26,22 +26,30 @@ import (
 	"golang.org/x/tools/go/analysis/passes/buildssa"
 )
 
+type testAnalyzerResult struct {
+	makeInterface []*ssa.MakeInterface
+	fieldAddr     []*ssa.FieldAddr
+}
+
 var testAnalyzer = &analysis.Analyzer{
 	Name:       "domination",
 	Run:        run,
 	Doc:        "test harness for de-referencing",
 	Requires:   []*analysis.Analyzer{buildssa.Analyzer},
-	ResultType: reflect.TypeOf([]*ssa.MakeInterface{}),
+	ResultType: reflect.TypeOf(testAnalyzerResult{}),
 }
 
 func run(pass *analysis.Pass) (interface{}, error) {
 	in := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
-	var result []*ssa.MakeInterface
+	var result testAnalyzerResult
 	for _, fn := range in.SrcFuncs {
 		for _, b := range fn.Blocks {
 			for _, i := range b.Instrs {
-				if mi, ok := i.(*ssa.MakeInterface); ok {
-					result = append(result, mi)
+				switch v := i.(type) {
+				case *ssa.MakeInterface:
+					result.makeInterface = append(result.makeInterface, v)
+				case *ssa.FieldAddr:
+					result.fieldAddr = append(result.fieldAddr, v)
 				}
 			}
 		}
@@ -77,15 +85,52 @@ func TestDereference(t *testing.T) {
 				t.Fatalf("Got len(result) == %d, want 1", len(r))
 			}
 
-			mis, ok := r[0].Result.([]*ssa.MakeInterface)
+			mis, ok := r[0].Result.(testAnalyzerResult)
 			if !ok {
-				t.Fatalf("Got result of type %T, wanted []*ssa.MakeInterface", mis)
+				t.Fatalf("Got result of type %T, wanted testAnalyzerResult", mis)
 			}
 
-			got := Dereference(mis[0].X.Type())
+			got := Dereference(mis.makeInterface[0].X.Type())
 			if !strings.HasSuffix(got.String(), tt.want) {
 				t.Fatalf("Got %s, want it to have a suffix of: %s", got, tt.want)
 			}
 		})
 	}
 }
+
+func TestFieldName(t *testing.T) {
+	dir := analysistest.TestData()
+
+	testCases := []struct {
+		pattern string
+		want    string
+	}{
+		{
+			pattern: "regular",
+			want:    "name",
+		},
+		{
+			pattern: "embedded",
+			want:    "foo",
+		},
+	}
+
+	for _, tt := range testCases {
+		t.Run(tt.pattern, func(t *testing.T) {
+			r := analysistest.Run(t, dir, testAnalyzer, fmt.Sprintf("fieldname/%s", tt.pattern))
+			if len(r) != 1 {
+				t.Fatalf("Got len(result) == %d, want 1", len(r))
+			}
+
+			a, ok := r[0].Result.(testAnalyzerResult)
+			if !ok {
+				t.Fatalf("Got result of type %T, wanted testAnalyzerResult", a)
+			}
+
+			got := FieldName(a.fieldAddr[0])
+			if got != tt.want {
+				t.Fatalf("Got %s, want %s", got, tt.want)
+			}
+		})
+	}
+}