summaryrefslogtreecommitdiff
path: root/libgo/go/cmd/vet/composite.go
blob: 861e040aac4e529bb6be21a5c211508fae2c64b1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// Copyright 2012 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.

// This file contains the test for unkeyed struct literals.

package main

import (
	"cmd/vet/internal/whitelist"
	"flag"
	"go/ast"
	"go/types"
	"strings"
)

var compositeWhiteList = flag.Bool("compositewhitelist", true, "use composite white list; for testing only")

func init() {
	register("composites",
		"check that composite literals of types from imported packages use field-keyed elements",
		checkUnkeyedLiteral,
		compositeLit)
}

// checkUnkeyedLiteral checks if a composite literal is a struct literal with
// unkeyed fields.
func checkUnkeyedLiteral(f *File, node ast.Node) {
	cl := node.(*ast.CompositeLit)

	typ := f.pkg.types[cl].Type
	if typ == nil {
		// cannot determine composite literals' type, skip it
		return
	}
	typeName := typ.String()
	if *compositeWhiteList && whitelist.UnkeyedLiteral[typeName] {
		// skip whitelisted types
		return
	}
	under := typ.Underlying()
	for {
		ptr, ok := under.(*types.Pointer)
		if !ok {
			break
		}
		under = ptr.Elem().Underlying()
	}
	if _, ok := under.(*types.Struct); !ok {
		// skip non-struct composite literals
		return
	}
	if isLocalType(f, typ) {
		// allow unkeyed locally defined composite literal
		return
	}

	// check if the CompositeLit contains an unkeyed field
	allKeyValue := true
	for _, e := range cl.Elts {
		if _, ok := e.(*ast.KeyValueExpr); !ok {
			allKeyValue = false
			break
		}
	}
	if allKeyValue {
		// all the composite literal fields are keyed
		return
	}

	f.Badf(cl.Pos(), "%s composite literal uses unkeyed fields", typeName)
}

func isLocalType(f *File, typ types.Type) bool {
	switch x := typ.(type) {
	case *types.Struct:
		// struct literals are local types
		return true
	case *types.Pointer:
		return isLocalType(f, x.Elem())
	case *types.Named:
		// names in package foo are local to foo_test too
		return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(f.pkg.path, "_test")
	}
	return false
}