├── .gitignore ├── .gitmodules ├── Makefile ├── README.md └── cmd └── interface-type-check ├── go.mod ├── go.sum ├── main.go └── main_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | interface-type-check.*.tar.gz 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "_go"] 2 | path = go 3 | url = https://github.com/siadat/go.git 4 | branch = interface-type-check 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: dependencies test build 2 | 3 | GOFORK = $(PWD)/go 4 | CHECKER = ./cmd/interface-type-check 5 | 6 | test: dependencies 7 | cd $(CHECKER) && GOROOT=$(GOFORK) $(GOFORK)/bin/go test -count=1 . 8 | 9 | build: dependencies 10 | cd $(CHECKER) && GOOS=darwin GOARCH=amd64 GOROOT=$(GOFORK) $(GOFORK)/bin/go build -o interface-type-check . 11 | tar cvzf interface-type-check.darwin-amd64.tar.gz $(CHECKER)/interface-type-check 12 | rm $(CHECKER)/interface-type-check 13 | 14 | cd $(CHECKER) && GOOS=linux GOARCH=amd64 GOROOT=$(GOFORK) $(GOFORK)/bin/go build -o interface-type-check . 15 | tar cvzf interface-type-check.linux-amd64.tar.gz $(CHECKER)/interface-type-check 16 | rm $(CHECKER)/interface-type-check 17 | 18 | cd $(CHECKER) && GOOS=windows GOARCH=amd64 GOROOT=$(GOFORK) $(GOFORK)/bin/go build -o interface-type-check . 19 | tar cvzf interface-type-check.windows-amd64.tar.gz $(CHECKER)/interface-type-check 20 | rm $(CHECKER)/interface-type-check 21 | 22 | dependencies: $(GOFORK)/bin/go 23 | cd $(CHECKER) && GOROOT=$(GOFORK) $(GOFORK)/bin/go get . 24 | 25 | $(GOFORK)/bin/go: 26 | cd $(GOFORK)/src && ./make.bash 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Static type checker for interface{} with a type list 2 | 3 | This is an experiment. 4 | 5 | * This is a tool that performs a static type check on values of type interface{}. 6 | * You specify the types in a special comment, eg `// #type T1, T2` 7 | * Internally, the implementation is based on go2go's Sum type. 8 | Go2go and this experiment have different concerns: go2go is about generic functions and type parameters, 9 | this experiment is about sum types. 10 | * For an alternative solution see the [siadat/group-interface](https://github.com/siadat/group-interface) pattern. 11 | 12 | ## Demo 13 | 14 | Without type checking: 15 | 16 | ```go 17 | /* */ package main 18 | /* */ 19 | /* */ type Numeric interface{} 20 | /* */ 21 | /* */ func main() { 22 | /* */ var n Numeric 23 | /* OK */ n = 3 24 | /* OK */ n = 3.14 25 | /* OK */ n = "abcd" 26 | /* */ _ = n 27 | /* */ } 28 | ``` 29 | 30 | With type checking: 31 | 32 | ```go 33 | /* */ package main 34 | /* */ 35 | /* */ type Numeric interface{ 36 | /* --> */ // #type int, float64 37 | /* */ } 38 | /* */ 39 | /* */ func main() { 40 | /* */ var n Numeric 41 | /* OK */ n = 3 42 | /* OK */ n = 3.14 43 | /* ERR */ n = "abcd" 44 | /* */ _ = n 45 | /* */ } 46 | ``` 47 | 48 | Execute the checker to get the error: 49 | 50 | ```bash 51 | $ interface-type-check . 52 | testfile.go:10:6: cannot use "bad value" (constant of type string) as Numeric value in variable declaration: mismatching sum type (have string, want a type in interface{type int, float64}) 53 | ``` 54 | ## Download 55 | 56 | Prebuilt binaries are available here as well as in the [release page](https://github.com/siadat/interface-type-check/releases/tag/v0.0.0). 57 | 58 | * Darwin: [download](https://github.com/siadat/interface-type-check/releases/download/v0.0.0/interface-type-check.darwin-amd64.tar.gz) (2.9 MB) 59 | * Linux: [download](https://github.com/siadat/interface-type-check/releases/download/v0.0.0/interface-type-check.linux-amd64.tar.gz) (2.96 MB) 60 | * Windows: [download](https://github.com/siadat/interface-type-check/releases/download/v0.0.0/interface-type-check.windows-amd64.tar.gz) (3.01 MB) 61 | 62 | ## Build 63 | 64 | ```bash 65 | git clone https://github.com/siadat/interface-type-check 66 | cd interface-type-check 67 | make test build 68 | ``` 69 | 70 | ## Checks 71 | 72 | Given the declaration: 73 | 74 | ```go 75 | type Numeric interface{ 76 | // #type int, float64 77 | } 78 | ``` 79 | 80 | The following checks are performed: 81 | 82 | ```go 83 | var number Numeric = "abc" // CHECK ERR: expected int or float 84 | ``` 85 | 86 | 87 | ```go 88 | _, _ = number.(string) // CHECK ERR: string not allowed 89 | ``` 90 | 91 | 92 | ```go 93 | switch number.(type) { 94 | case string: // CHECK ERR: string not allowed 95 | case float: 96 | } 97 | ``` 98 | 99 | 100 | ```go 101 | switch number.(type) { // CHECK ERR: missing case for int 102 | case float: 103 | } 104 | ``` 105 | 106 | ```go 107 | switch number.(type) { // CHECK ERR: missing case for nil 108 | case float: 109 | case int: 110 | } 111 | ``` 112 | 113 | More examples: fork/[src/types/examples/sum.go2](https://github.com/siadat/go/blob/interface-type-check/src/go/types/examples/sum.go2) 114 | 115 | 121 | 122 | ## Experiment: json.Token 123 | 124 | All supported types of encoding/json.Token are known, 125 | as documented [here](https://pkg.go.dev/encoding/json?tab=doc#Token): 126 | 127 | ```go 128 | // A Token holds a value of one of these types: 129 | // 130 | // Delim, for the four JSON delimiters [ ] { } 131 | // bool, for JSON booleans 132 | // float64, for JSON numbers 133 | // Number, for JSON numbers 134 | // string, for JSON string literals 135 | // nil, for JSON null 136 | // 137 | type Token interface{} 138 | ``` 139 | 140 | Adding the #type comment, it would look like this: 141 | 142 | ```go 143 | type Token interface { 144 | // #type Delim, bool, float64, Number, string 145 | } 146 | ``` 147 | 148 | That's all we need to be able to use the checker. 149 | 150 | ## Experiment: sql.Scanner 151 | 152 | database/sql.Scanner is also [defined](https://pkg.go.dev/database/sql?tab=doc#Scanner) 153 | as an empty interface whose possible types are known. 154 | 155 | Before: 156 | 157 | ```go 158 | // Scanner is an interface used by Scan. 159 | type Scanner interface { 160 | // Scan assigns a value from a database driver. 161 | // 162 | // The src value will be of one of the following types: 163 | // 164 | // int64 165 | // float64 166 | // bool 167 | // []byte 168 | // string 169 | // time.Time 170 | // nil - for NULL values 171 | // 172 | Scan(src interface{}) error 173 | } 174 | ``` 175 | 176 | After: 177 | 178 | ```go 179 | // Scanner is an interface used by Scan. 180 | type Scanner interface { 181 | Scan(src SourceType) error 182 | } 183 | 184 | type SourceType interface { 185 | // #type int64, float64, bool, []byte, string, time.Time 186 | } 187 | ``` 188 | 189 | 190 | 207 | 208 | 209 | ## Experiment: net.IP 210 | 211 | The standard library defines one [net.IP](https://pkg.go.dev/net#IP) type for both IPv4 and IPv6 IPs: 212 | 213 | ```go 214 | // An IP is a single IP address, a slice of bytes. 215 | // Functions in this package accept either 4-byte (IPv4) 216 | // or 16-byte (IPv6) slices as input. 217 | type IP []byte 218 | ``` 219 | 220 | This type has a String() function, which relies on runtime checks to detect the version of the IP [here](https://github.com/golang/go/blob/edfd6f28486017dcb136cd3f3ec252706d4b326e/src/net/ip.go#L299): 221 | 222 | ```go 223 | if p4 := p.To4(); len(p4) == IPv4len { ... 224 | ``` 225 | 226 | There are very good reasons to use a simple []byte data structure for the IPs. 227 | I am *not* suggesting that this code should change. 228 | I am only running tiny hypothetical experiments. With that in mind, let's write it using `// #type`: 229 | 230 | ```go 231 | type IPv4 [4]byte 232 | type IPv6 [16]byte 233 | 234 | type IP interface { 235 | // #type IPv4, IPv6 236 | } 237 | 238 | func version(ip IP) int { 239 | switch ip.(type) { 240 | case IPv4: return 4 241 | case IPv6: return 6 242 | case nil: panic("ip is nil") 243 | } 244 | } 245 | ``` 246 | 247 | ## Experiment: a hypothetical connection object 248 | 249 | The Connecting type has a retry field: 250 | 251 | ```go 252 | type Connected struct{} 253 | type Disconnected struct{} 254 | type Connecting struct{ rety int } 255 | 256 | type Connection interface { 257 | // #type Connected, Disconnected, Connecting 258 | } 259 | 260 | func log(conn Connection) int { 261 | switch c := conn.(type) { 262 | case Connected: fmt.Println("Connected") 263 | case Disconnected: fmt.Println("Disconnected") 264 | case Connecting: fmt.Println("Connecting, retry:", c.retry) 265 | case nil: panic("conn is nil") 266 | } 267 | } 268 | ``` 269 | 270 | ## When to use / When not to use 271 | 272 | Empty interfaces are used when we want to store variables of different types 273 | which don't implement a common interface. 274 | 275 | There are two general use cases of an empty interface: 276 | 277 | 1. supported types are unknown (eg json.Marshal) 278 | 2. supported types are known (eg json.Token) 279 | 280 | ### Don't use if: 281 | 282 | You should not use this checker for 1. 283 | Sometimes we do not have prior knowledge about the expected types. 284 | For example, json.Marshal(v interface{}) is designed to accept 285 | structs of any type. This function uses reflect to gather information 286 | it needs about the type of v. 287 | In this case, it is not possible to list all the supported types. 288 | 289 | ### Use if: 290 | 291 | You could consider using it, when all the types you support 292 | are known at the type of writing your code. 293 | 294 | This is particularly useful when the types are primitives (eg int), 295 | where we have to create a new wrapper type (eg type Int int) and 296 | implement a non-empty interface on it. 297 | 298 | ## Go2's type list 299 | 300 | This tool is designed to work with code written in the current versions of Go (ie Go1). 301 | The current design draft of Go2 includes the type list: 302 | 303 | ```go 304 | type Numeric interface { 305 | type int, float64 306 | } 307 | ``` 308 | 309 | At the moment, the type list is intended for function type parameters only. 310 | 311 | ``` 312 | interface type for variable cannot contain type constraints 313 | ``` 314 | 315 | The draft [notes](https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#type-lists-in-interface-types): 316 | 317 | > Interface types with type lists may only be used as constraints on type 318 | > parameters. They may not be used as ordinary interface types. The same is true 319 | > of the predeclared interface type comparable. 320 | > 321 | > **This restriction may be lifted in future language versions. An interface type 322 | > with a type list may be useful as a form of sum type, albeit one that can have 323 | > the value nil**. Some alternative syntax would likely be required to match on 324 | > identical types rather than on underlying types; perhaps type ==. For now, this 325 | > is not permitted. 326 | 327 | The highlight section is what this experiment addresses via an external type checking tool. 328 | 329 | You might think of this tool as an experiment to see whether a sum type would be a valuable addition to the language. 330 | 331 | ## Implementation 332 | 333 | - This experiment is built on top of the dev.go2go branch and uses types.Sum. See [diff](https://github.com/siadat/go/commit/af8a19e4de0c689be9d898d7ca3b0b5fd51767cb). 334 | - A few more test examples are added to [types/examples](https://github.com/siadat/go/commit/af8a19e4de0c689be9d898d7ca3b0b5fd51767cb#diff-4204251ae72f5797f67f5f4393ab8c10). 335 | 336 | ## Limitations 337 | 338 | - Only single line comments are implemented, ie `// #type T1, T2` 339 | - Zero-value for an interface is nil. 340 | Several approaches come to mind: 341 | - allow nil values. 342 | - allow nil values, but fail if type switch statements don't include nil (what we do in this checker). 343 | - track all initializations/assignments/etc of the interfaces with types and fail if they are nil. 344 | - change the zero-value of an interface with a type list to be the zero-value of its first type (or some type chosen by the programmer). 345 | 346 | ## Contribute 347 | 348 | Do any of these: 349 | 350 | - Download a binary, or build from source. 351 | - Report issues. You will most likely run into problems, because this is a new project. 352 | - Use it! Let me know what you use it for. 353 | - Search for TODOs in the code. 354 | - Implement missing features. 355 | 356 | 357 | 358 | -------------------------------------------------------------------------------- /cmd/interface-type-check/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/siadat/interface-type-check/cmd/interface-type-check 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/stretchr/testify v1.6.1 7 | golang.org/x/mod v0.3.0 // indirect 8 | golang.org/x/tools v0.0.0-20200616205136-76e917206452 9 | ) 10 | -------------------------------------------------------------------------------- /cmd/interface-type-check/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 7 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 8 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 9 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 10 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 11 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 12 | golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= 13 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 14 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 15 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 16 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 17 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 18 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 19 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 20 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 21 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 22 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 23 | golang.org/x/tools v0.0.0-20200616205136-76e917206452 h1:mSfCam1kRgTRL/Y21Ws8mPiFrKdw9mjkWh8/TtaYUDw= 24 | golang.org/x/tools v0.0.0-20200616205136-76e917206452/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 25 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 26 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 27 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 28 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 29 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 30 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 31 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 32 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 33 | -------------------------------------------------------------------------------- /cmd/interface-type-check/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | 8 | "golang.org/x/tools/go/packages" 9 | ) 10 | 11 | func main() { 12 | conf := &packages.Config{ 13 | Mode: packages.LoadAllSyntax, 14 | Tests: true, 15 | } 16 | 17 | paths := os.Args[1:] 18 | 19 | if len(paths) == 0 { 20 | paths = []string{"."} 21 | } 22 | pkgs, err := packages.Load(conf, paths...) 23 | if err != nil { 24 | panic(err) 25 | } 26 | runtime.GC() 27 | 28 | for _, pkg := range pkgs { 29 | if pkg.IllTyped { 30 | for _, err := range pkg.Errors { 31 | fmt.Printf("error: %v\n", err.Error()) 32 | } 33 | return 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /cmd/interface-type-check/main_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "go/ast" 7 | "go/importer" 8 | "go/parser" 9 | "go/token" 10 | "go/types" 11 | "strings" 12 | "testing" 13 | 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | func addLinenumbers(src string) string { 18 | lines := strings.Split(src, "\n") 19 | numberedLines := make([]string, len(lines)) 20 | for i := range lines { 21 | numberedLines[i] = fmt.Sprintf("%3d %s", i+1, lines[i]) 22 | } 23 | return strings.Join(numberedLines, "\n") 24 | } 25 | 26 | func TestParserAndTypes(t *testing.T) { 27 | testCases := []struct { 28 | src string 29 | typeErr error 30 | parseErr error 31 | }{ 32 | { 33 | src: ` 34 | package whatever 35 | 36 | type MyC interface { 37 | // #type string 38 | } 39 | `, 40 | typeErr: nil, 41 | }, 42 | { 43 | src: ` 44 | package whatever 45 | 46 | type MyC interface { 47 | // #type string, int 48 | } 49 | `, 50 | typeErr: nil, 51 | }, 52 | { 53 | src: ` 54 | package whatever 55 | 56 | type MyC interface { 57 | // #type string, int, T 58 | } 59 | `, 60 | typeErr: errors.New(`testfile.go:5:24: undeclared name: T`), 61 | }, 62 | { 63 | src: ` 64 | package whatever 65 | 66 | type T struct { 67 | Name string 68 | } 69 | 70 | type MyC interface { 71 | // #type string, int, T 72 | } 73 | `, 74 | typeErr: nil, 75 | }, 76 | { 77 | src: ` 78 | package whatever 79 | 80 | type T struct { 81 | Name string 82 | } 83 | 84 | type MyC interface { 85 | // #type T, string, int, MyC 86 | } 87 | `, 88 | typeErr: nil, 89 | }, 90 | { 91 | src: ` 92 | package whatever 93 | 94 | type T struct { 95 | Name string 96 | } 97 | 98 | type MyC2 interface { 99 | // #type bool, struct{} 100 | } 101 | 102 | type MyC1 interface { 103 | // #type T, string, int, MyC2 104 | } 105 | `, 106 | typeErr: nil, 107 | }, 108 | { 109 | src: ` 110 | package whatever 111 | 112 | type MyI interface { 113 | } 114 | 115 | func Handle(c MyI) { 116 | switch c.(type) { 117 | case string: 118 | } 119 | } 120 | `, 121 | typeErr: nil, 122 | }, 123 | { 124 | src: ` 125 | package whatever 126 | 127 | type MyC interface {/* #type string, int */} 128 | 129 | func Handle(c MyC) { 130 | switch c.(type) { 131 | case string, int, nil: 132 | } 133 | } 134 | `, 135 | typeErr: nil, 136 | }, 137 | { 138 | src: ` 139 | package whatever 140 | 141 | type MyC interface { 142 | // #type string, int 143 | } 144 | 145 | func Handle(c MyC) { 146 | switch c.(type) { 147 | default: 148 | } 149 | } 150 | `, 151 | typeErr: nil, 152 | }, 153 | { 154 | src: ` 155 | package whatever 156 | 157 | type MyC interface { /* #type string, int */ } 158 | 159 | func Handle(c MyC) { 160 | switch c.(type) { 161 | case string, int: 162 | } 163 | } 164 | `, 165 | typeErr: nil, 166 | }, 167 | { 168 | src: ` 169 | package whatever 170 | 171 | type MyC interface { 172 | // #type string 173 | } 174 | 175 | func Handle(c MyC) { 176 | switch c.(type) { 177 | case bool: 178 | } 179 | } 180 | `, 181 | typeErr: errors.New("testfile.go:10:7: c (variable of type MyC) cannot have dynamic type bool (mismatching sum assertion)"), 182 | }, 183 | { 184 | src: ` 185 | package whatever 186 | 187 | type MyC interface { 188 | // #type string 189 | } 190 | 191 | func Handle(c MyC) { 192 | c = 5 193 | } 194 | `, 195 | typeErr: errors.New("testfile.go:9:6: cannot use 5 (constant of type int) as MyC value in assignment: mismatching sum type (have int, want a type in interface{type string})"), 196 | }, 197 | { 198 | src: ` 199 | package whatever 200 | 201 | type MyC interface { 202 | // #type string 203 | } 204 | 205 | func Handle(c MyC) { 206 | switch c.(type) { 207 | case string: 208 | } 209 | } 210 | `, 211 | typeErr: errors.New("testfile.go:9:9: missing sum case nil in type switch"), 212 | }, 213 | { 214 | src: ` 215 | package whatever 216 | 217 | type MyC interface { 218 | // #type string, int 219 | } 220 | 221 | func Handle(c MyC) { 222 | switch c.(type) { 223 | case string, int: 224 | } 225 | } 226 | `, 227 | typeErr: errors.New("testfile.go:9:9: missing sum case nil in type switch"), 228 | }, 229 | { 230 | src: ` 231 | package whatever 232 | 233 | type MyC interface { 234 | // #type string, int 235 | } 236 | 237 | func Handle(c MyC) { 238 | switch c.(type) { 239 | case string, nil: 240 | } 241 | } 242 | `, 243 | typeErr: errors.New("testfile.go:9:9: missing sum case int in type switch"), 244 | }, 245 | { 246 | src: ` 247 | package whatever 248 | 249 | type MyC interface { 250 | // #type string, int 251 | } 252 | 253 | func Handle(c MyC) { 254 | switch c.(type) { 255 | case nil: 256 | } 257 | } 258 | `, 259 | typeErr: errors.New("testfile.go:9:9: missing sum case string in type switch"), 260 | }, 261 | { 262 | src: ` 263 | package whatever 264 | 265 | type MyC interface { 266 | // #type string, int 267 | } 268 | 269 | func Handle(c MyC) { 270 | switch c.(type) { 271 | case string, int, nil, bool: 272 | } 273 | } 274 | `, 275 | typeErr: errors.New("testfile.go:10:25: c (variable of type MyC) cannot have dynamic type bool (mismatching sum assertion)"), 276 | }, 277 | { 278 | src: ` 279 | package whatever 280 | 281 | type MyC interface { 282 | // #type string, int 283 | } 284 | 285 | func Handle(c MyC) { 286 | _ = c.(bool) 287 | } 288 | `, 289 | typeErr: errors.New("testfile.go:9:6: c (variable of type MyC) cannot have dynamic type bool (mismatching sum assertion)"), 290 | }, 291 | { 292 | src: ` 293 | package whatever 294 | 295 | type MyC interface { 296 | // #type string, int 297 | } 298 | 299 | func Handle(c MyC) { 300 | _ = c.(int) 301 | } 302 | `, 303 | typeErr: nil, 304 | }, 305 | { 306 | src: ` 307 | package whatever 308 | 309 | type MyC interface { 310 | // #type string, int 311 | } 312 | 313 | func main() { 314 | var x MyC = "hello" 315 | _ = x 316 | 317 | var y MyC = 5 318 | _ = y 319 | } 320 | `, 321 | typeErr: nil, 322 | }, 323 | { 324 | src: ` 325 | package whatever 326 | 327 | type MyC interface { 328 | // #type string, int 329 | } 330 | 331 | func main() { 332 | var x MyC = 1.1 333 | _ = x 334 | } 335 | `, 336 | typeErr: errors.New("testfile.go:9:14: cannot use 1.1 (constant of type float64) as MyC value in variable declaration: mismatching sum type (have float64, want a type in interface{type string, int})"), 337 | }, 338 | { 339 | src: ` 340 | package whatever 341 | 342 | type MyS struct { 343 | Name string 344 | } 345 | type MyC interface { 346 | // #type bool, MyS 347 | } 348 | 349 | func main() { 350 | var x MyC = MyS{Name: "hi"} 351 | switch xx := x.(type) { 352 | case bool: _ = xx 353 | case MyS: _ = xx.Name 354 | case nil: 355 | } 356 | } 357 | `, 358 | typeErr: nil, 359 | }, 360 | { 361 | src: ` 362 | package whatever 363 | 364 | //#type string, int,,,, 365 | // # type string, int,,,, 366 | // #typestring, int,,,, 367 | 368 | type MyC1 interface { 369 | //#type string, int,,,, 370 | } 371 | 372 | type MyC2 interface { 373 | // # type string, int,,,, 374 | } 375 | 376 | type MyC3 interface { 377 | // #typestring, int,,,, 378 | } 379 | `, 380 | parseErr: nil, 381 | typeErr: nil, 382 | }, 383 | { 384 | src: ` 385 | package whatever 386 | // #type string, int 387 | `, 388 | parseErr: errors.New(`testfile.go:3:16: expected type, found ','`), 389 | typeErr: nil, 390 | }, 391 | { 392 | src: ` 393 | package whatever 394 | 395 | type MyC interface { 396 | // #type int, 397 | // string 398 | } 399 | `, 400 | parseErr: errors.New(`testfile.go:7:1: expected type, found '}'`), 401 | }, 402 | { 403 | src: ` 404 | package whatever 405 | 406 | type T struct { 407 | Name string 408 | } 409 | 410 | type MyC interface { 411 | // #type T, string, int 412 | // #type bool 413 | } 414 | `, 415 | typeErr: errors.New("testfile.go:10:6: cannot have multiple type lists in an interface"), 416 | }, 417 | { 418 | src: ` 419 | package whatever 420 | 421 | // import "fmt" 422 | 423 | var x = 3.14 424 | var xx MyC // should fail 425 | 426 | func main() { 427 | // switch (interface{}(nil)).(type) { 428 | // case int, bool, string: 429 | // } 430 | 431 | var a int 432 | // var c MjijijyC // should fail 433 | // var c MyC // should fail 434 | 435 | var c = "hola" // ok 436 | 437 | // var c interface{string; int} // should fail 438 | // c := T{Name: "sina"} // ok 439 | // c := T2{Name: "sina"} // should fail 440 | // c := MyC{} // should fail 441 | 442 | _ = a 443 | _ = c 444 | 445 | DoC(c) 446 | } 447 | 448 | type MyI interface { 449 | Method1() 450 | Method2() 451 | } 452 | 453 | type T struct { 454 | Name string 455 | } 456 | 457 | type T2 struct { 458 | Name string 459 | } 460 | 461 | type MyC interface { 462 | // #type T, string, int 463 | } 464 | 465 | type MyC2 interface{} 466 | 467 | func DoC(c MyC) (x MyC) { 468 | // c = 3 // ok fail 469 | // c = 3.2 // should fail 470 | 471 | // err := fmt.Errorf("ok?") 472 | // switch err.(type) { 473 | // case int: 474 | // fmt.Println("INT") 475 | // } 476 | 477 | switch c.(type) { 478 | case int: 479 | case string: 480 | case T: 481 | case nil: 482 | // case bool: // err 483 | // default: // ok even when missing a case 484 | } 485 | 486 | 487 | return 488 | } 489 | 490 | func DoI(c MyI) { 491 | } 492 | `, 493 | typeErr: nil, 494 | }, 495 | } 496 | 497 | for ti, tt := range testCases { 498 | // src := tt.src 499 | src := func() string { 500 | lines := strings.Split(tt.src, "\n") 501 | indent := "" 502 | 503 | for i := range lines { 504 | if strings.TrimSpace(lines[i]) == "package whatever" { 505 | indent = strings.TrimSuffix(lines[i], "package whatever") 506 | break 507 | } 508 | } 509 | 510 | for i := range lines { 511 | lines[i] = strings.TrimPrefix(lines[i], indent) 512 | } 513 | 514 | return strings.Join(lines, "\n") 515 | }() 516 | 517 | fset := token.NewFileSet() 518 | f, err := parser.ParseFile(fset, "testfile.go", src, parser.ParseComments) 519 | if tt.parseErr == nil { 520 | require.NoError(t, err, fmt.Sprintf("case %d\n", ti)+addLinenumbers(src)) 521 | } else { 522 | require.Error(t, err, fmt.Sprintf("case %d\n", ti)+addLinenumbers(src)) 523 | require.Equal(t, tt.parseErr.Error(), err.Error(), fmt.Sprintf("case %d\n", ti)+addLinenumbers(src)) 524 | } 525 | 526 | info := types.Info{ 527 | Types: make(map[ast.Expr]types.TypeAndValue), 528 | Defs: make(map[*ast.Ident]types.Object), 529 | Uses: make(map[*ast.Ident]types.Object), 530 | } 531 | conf := types.Config{Importer: importer.Default()} 532 | _, err = conf.Check("testfile", fset, []*ast.File{f}, &info) 533 | if tt.typeErr == nil { 534 | require.NoError(t, err, fmt.Sprintf("case %d\n", ti)+addLinenumbers(src)) 535 | } else { 536 | require.Error(t, err, fmt.Sprintf("case %d\n", ti)+addLinenumbers(src)) 537 | require.Equal(t, tt.typeErr.Error(), err.Error(), fmt.Sprintf("case %d\n", ti)+addLinenumbers(src)) 538 | } 539 | 540 | } 541 | } 542 | --------------------------------------------------------------------------------