{"id":16476625,"url":"https://github.com/siadat/interface-type-check","last_synced_at":"2025-10-07T23:42:06.908Z","repository":{"id":64303201,"uuid":"286577196","full_name":"siadat/interface-type-check","owner":"siadat","description":"Type check the empty interface{}","archived":false,"fork":false,"pushed_at":"2020-08-14T07:35:07.000Z","size":40,"stargazers_count":13,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-29T19:58:18.815Z","etag":null,"topics":["golang","type-checking"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/siadat.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-08-10T20:56:22.000Z","updated_at":"2024-07-05T12:34:32.000Z","dependencies_parsed_at":"2023-01-15T10:00:44.924Z","dependency_job_id":null,"html_url":"https://github.com/siadat/interface-type-check","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/siadat/interface-type-check","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/siadat%2Finterface-type-check","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/siadat%2Finterface-type-check/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/siadat%2Finterface-type-check/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/siadat%2Finterface-type-check/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/siadat","download_url":"https://codeload.github.com/siadat/interface-type-check/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/siadat%2Finterface-type-check/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278866783,"owners_count":26059669,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-07T02:00:06.786Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","type-checking"],"created_at":"2024-10-11T12:43:07.069Z","updated_at":"2025-10-07T23:42:06.885Z","avatar_url":"https://github.com/siadat.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Static type checker for interface{} with a type list\n\nThis is an experiment.\n\n* This is a tool that performs a static type check on values of type interface{}.\n* You specify the types in a special comment, eg `// #type T1, T2`\n* Internally, the implementation is based on go2go's Sum type.\n  Go2go and this experiment have different concerns: go2go is about generic functions and type parameters,\n  this experiment is about sum types.\n* For an alternative solution see the [siadat/group-interface](https://github.com/siadat/group-interface) pattern.\n\n## Demo\n\nWithout type checking:\n\n```go\n/*    */  package main\n/*    */\n/*    */  type Numeric interface{}\n/*    */  \n/*    */  func main() {\n/*    */  \tvar n Numeric\n/* OK */  \tn = 3\n/* OK */  \tn = 3.14\n/* OK */  \tn = \"abcd\"\n/*    */  \t_ = n\n/*    */  }\n```\n\nWith type checking:\n\n```go\n/*     */  package main\n/*     */\n/*     */  type Numeric interface{\n/* --\u003e */  \t// #type int, float64\n/*     */  }\n/*     */  \n/*     */  func main() {\n/*     */  \tvar n Numeric\n/* OK  */  \tn = 3\n/* OK  */  \tn = 3.14\n/* ERR */  \tn = \"abcd\"\n/*     */  \t_ = n\n/*     */  }\n```\n\nExecute the checker to get the error:\n\n```bash\n$ interface-type-check .\ntestfile.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})\n```\n## Download\n\nPrebuilt binaries are available here as well as in the [release page](https://github.com/siadat/interface-type-check/releases/tag/v0.0.0).\n\n* Darwin: [download](https://github.com/siadat/interface-type-check/releases/download/v0.0.0/interface-type-check.darwin-amd64.tar.gz) (2.9 MB)\n* Linux: [download](https://github.com/siadat/interface-type-check/releases/download/v0.0.0/interface-type-check.linux-amd64.tar.gz) (2.96 MB)\n* Windows: [download](https://github.com/siadat/interface-type-check/releases/download/v0.0.0/interface-type-check.windows-amd64.tar.gz) (3.01 MB)\n\n## Build\n\n```bash\ngit clone https://github.com/siadat/interface-type-check\ncd interface-type-check\nmake test build\n```\n\n## Checks\n\nGiven the declaration:\n\n```go\ntype Numeric interface{\n\t// #type int, float64\n}\n```\n\nThe following checks are performed:\n\n```go\nvar number Numeric = \"abc\" // CHECK ERR: expected int or float\n```\n\n\n```go\n_, _ = number.(string)     // CHECK ERR: string not allowed\n```\n\n\n```go\nswitch number.(type) {\ncase string:               // CHECK ERR: string not allowed\ncase float:\n}\n```\n\n\n```go\nswitch number.(type) {     // CHECK ERR: missing case for int\ncase float:\n}\n```\n\n```go\nswitch number.(type) {     // CHECK ERR: missing case for nil\ncase float:\ncase int:\n}\n```\n\nMore examples: fork/[src/types/examples/sum.go2](https://github.com/siadat/go/blob/interface-type-check/src/go/types/examples/sum.go2)\n\n\u003c!--\n### src/cmd/compile/internal/ssa/gen/rulegen.go Node and Statement\n### database/sql/driver.Value\n### plugin.Symbol\n### xml.Token\n--\u003e\n\n## Experiment: json.Token\n\nAll supported types of encoding/json.Token are known,\nas documented [here](https://pkg.go.dev/encoding/json?tab=doc#Token):\n\n```go\n// A Token holds a value of one of these types:\n//\n//\tDelim, for the four JSON delimiters [ ] { }\n//\tbool, for JSON booleans\n//\tfloat64, for JSON numbers\n//\tNumber, for JSON numbers\n//\tstring, for JSON string literals\n//\tnil, for JSON null\n//\ntype Token interface{}\n```\n\nAdding the #type comment, it would look like this:\n\n```go\ntype Token interface {\n\t// #type Delim, bool, float64, Number, string\n}\n```\n\nThat's all we need to be able to use the checker.\n\n## Experiment: sql.Scanner\n\ndatabase/sql.Scanner is also [defined](https://pkg.go.dev/database/sql?tab=doc#Scanner)\nas an empty interface whose possible types are known.\n\nBefore:\n\n```go\n// Scanner is an interface used by Scan.\ntype Scanner interface {\n\t// Scan assigns a value from a database driver.\n\t//\n\t// The src value will be of one of the following types:\n\t//\n\t//    int64\n\t//    float64\n\t//    bool\n\t//    []byte\n\t//    string\n\t//    time.Time\n\t//    nil - for NULL values\n\t//\n\tScan(src interface{}) error\n}\n```\n\nAfter:\n\n```go\n// Scanner is an interface used by Scan.\ntype Scanner interface {\n\tScan(src SourceType) error\n}\n\ntype SourceType interface {\n\t// #type int64, float64, bool, []byte, string, time.Time\n}\n```\n\n\n\u003c!--\n## Experiment: error handling\n\n```go\ntype RequestOrError interface {\n\t// #type *http.Request, error\n}\n\nfunc H(re RequestOrError) {\n\tswitch x := re.(type) {\n\tcase *http.Request: fmt.Println(\"received request\", x)\n\tcase error:         fmt.Println(\"received error\", x)\n\tcase nil:           fmt.Println(\"received nil\")\n\t}\n}\n```\n--\u003e\n\n\n## Experiment: net.IP\n\nThe standard library defines one [net.IP](https://pkg.go.dev/net#IP) type for both IPv4 and IPv6 IPs:\n\n```go\n// An IP is a single IP address, a slice of bytes.\n// Functions in this package accept either 4-byte (IPv4)\n// or 16-byte (IPv6) slices as input.\ntype IP []byte\n```\n\nThis 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):\n\n```go\nif p4 := p.To4(); len(p4) == IPv4len { ...\n```\n\nThere are very good reasons to use a simple []byte data structure for the IPs.\nI am *not* suggesting that this code should change.\nI am only running tiny hypothetical experiments. With that in mind, let's write it using `// #type`:\n\n```go\ntype IPv4 [4]byte\ntype IPv6 [16]byte\n\ntype IP interface {\n\t// #type IPv4, IPv6\n}\n\nfunc version(ip IP) int {\n\tswitch ip.(type) {\n\tcase IPv4: return 4\n\tcase IPv6: return 6\n\tcase nil:  panic(\"ip is nil\")\n\t}\n}\n```\n\n## Experiment: a hypothetical connection object\n\nThe Connecting type has a retry field:\n\n```go\ntype Connected    struct{}\ntype Disconnected struct{}\ntype Connecting   struct{ rety int }\n\ntype Connection interface {\n\t// #type Connected, Disconnected, Connecting\n}\n\nfunc log(conn Connection) int {\n\tswitch c := conn.(type) {\n\tcase Connected:    fmt.Println(\"Connected\")\n\tcase Disconnected: fmt.Println(\"Disconnected\")\n\tcase Connecting:   fmt.Println(\"Connecting, retry:\", c.retry)\n\tcase nil:          panic(\"conn is nil\")\n\t}\n}\n```\n\n## When to use / When not to use\n\nEmpty interfaces are used when we want to store variables of different types\nwhich don't implement a common interface.\n\nThere are two general use cases of an empty interface:\n\n1. supported types are unknown (eg json.Marshal)\n2. supported types are known (eg json.Token)\n\n### Don't use if:\n\nYou should not use this checker for 1.\nSometimes we do not have prior knowledge about the expected types.\nFor example, json.Marshal(v interface{}) is designed to accept\nstructs of any type. This function uses reflect to gather information\nit needs about the type of v.\nIn this case, it is not possible to list all the supported types.\n\n### Use if:\n\nYou could consider using it, when all the types you support\nare known at the type of writing your code.\n\nThis is particularly useful when the types are primitives (eg int),\nwhere we have to create a new wrapper type (eg type Int int) and\nimplement a non-empty interface on it.\n\n## Go2's type list\n\nThis tool is designed to work with code written in the current versions of Go (ie Go1).\nThe current design draft of Go2 includes the type list:\n\n```go\ntype Numeric interface {\n\ttype int, float64\n}\n```\n\nAt the moment, the type list is intended for function type parameters only.\n\n```\ninterface type for variable cannot contain type constraints\n```\n\nThe draft [notes](https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#type-lists-in-interface-types):\n\n\u003e Interface types with type lists may only be used as constraints on type\n\u003e parameters. They may not be used as ordinary interface types. The same is true\n\u003e of the predeclared interface type comparable.\n\u003e \n\u003e **This restriction may be lifted in future language versions. An interface type\n\u003e with a type list may be useful as a form of sum type, albeit one that can have\n\u003e the value nil**. Some alternative syntax would likely be required to match on\n\u003e identical types rather than on underlying types; perhaps type ==. For now, this\n\u003e is not permitted.\n\nThe highlight section is what this experiment addresses via an external type checking tool.\n\nYou might think of this tool as an experiment to see whether a sum type would be a valuable addition to the language.\n\n## Implementation\n\n- This experiment is built on top of the dev.go2go branch and uses types.Sum. See [diff](https://github.com/siadat/go/commit/af8a19e4de0c689be9d898d7ca3b0b5fd51767cb).\n- A few more test examples are added to [types/examples](https://github.com/siadat/go/commit/af8a19e4de0c689be9d898d7ca3b0b5fd51767cb#diff-4204251ae72f5797f67f5f4393ab8c10).\n\n## Limitations\n\n- Only single line comments are implemented, ie `// #type T1, T2`\n- Zero-value for an interface is nil.\n  Several approaches come to mind:\n  - allow nil values.\n  - allow nil values, but fail if type switch statements don't include nil (what we do in this checker).\n  - track all initializations/assignments/etc of the interfaces with types and fail if they are nil.\n  - 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).\n\n## Contribute\n\nDo any of these:\n\n- Download a binary, or build from source.\n- Report issues. You will most likely run into problems, because this is a new project.\n- Use it! Let me know what you use it for.\n- Search for TODOs in the code.\n- Implement missing features.\n\n\u003c!-- https://github.com/golang/go/compare/dev.go2go...siadat:interface-type-check --\u003e\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsiadat%2Finterface-type-check","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsiadat%2Finterface-type-check","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsiadat%2Finterface-type-check/lists"}