Generate atomic integers automatically

Rather than hand-writing the atomic integers, generate them
automatically from the same template. The generator is at
internal/gen-atomicint.

To ensure these are never out of date, add another `make` target which
verifies that the working tree is left unchanged after regenerating
code.
diff --git a/.codecov.yml b/.codecov.yml
index 6d4d1be..047c0cc 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -13,3 +13,5 @@
         if_not_found: success  # if parent is not found report status as success, error, or failure
         if_ci_failed: error    # if ci fails report status as success, error, or failure
 
+ignore:
+  - /internal/gen-atomicint/
diff --git a/Makefile b/Makefile
index 39af0fb..5e0ec1c 100644
--- a/Makefile
+++ b/Makefile
@@ -2,8 +2,9 @@
 export GOBIN ?= $(shell pwd)/bin
 
 GOLINT = $(GOBIN)/golint
+GEN_ATOMICINT = $(GOBIN)/gen-atomicint
 
-GO_FILES ?= *.go
+GO_FILES ?= $(shell find . '(' -path .git -o -path vendor ')' -prune -o -name '*.go' -print)
 
 .PHONY: build
 build:
@@ -22,14 +23,33 @@
 $(GOLINT):
 	go install golang.org/x/lint/golint
 
+$(GEN_ATOMICINT): $(wildcard ./internal/gen-atomicint/*)
+	go build -o $@ ./internal/gen-atomicint
+
 .PHONY: golint
 golint: $(GOLINT)
 	$(GOLINT) ./...
 
 .PHONY: lint
-lint: gofmt golint
+lint: gofmt golint generatenodirty
 
 .PHONY: cover
 cover:
 	go test -coverprofile=cover.out -coverpkg ./... -v ./...
 	go tool cover -html=cover.out -o cover.html
+
+.PHONY: generate
+generate: $(GEN_ATOMICINT)
+	go generate ./...
+
+.PHONY: generatenodirty
+generatenodirty:
+	@[ -z "$$(git status --porcelain)" ] || ( \
+		echo "Working tree is dirty. Commit your changes first."; \
+		exit 1 )
+	@make generate
+	@status=$$(git status --porcelain); \
+		[ -z "$$status" ] || ( \
+		echo "Working tree is dirty after `make generate`:"; \
+		echo "$$status"; \
+		echo "Please ensure that the generated code is up-to-date." )
diff --git a/gen.go b/gen.go
new file mode 100644
index 0000000..50d6b24
--- /dev/null
+++ b/gen.go
@@ -0,0 +1,26 @@
+// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+//go:generate bin/gen-atomicint -name=Int32 -wrapped=int32 -file=int32.go
+//go:generate bin/gen-atomicint -name=Int64 -wrapped=int64 -file=int64.go
+//go:generate bin/gen-atomicint -name=Uint32 -wrapped=uint32 -unsigned -file=uint32.go
+//go:generate bin/gen-atomicint -name=Uint64 -wrapped=uint64 -unsigned -file=uint64.go
diff --git a/int32.go b/int32.go
index d31191e..cdd2844 100644
--- a/int32.go
+++ b/int32.go
@@ -25,10 +25,10 @@
 	"sync/atomic"
 )
 
-// Int32 is an atomic wrapper around an int32.
+// Int32 is an atomic wrapper around int32.
 type Int32 struct{ v int32 }
 
-// NewInt32 creates an Int32.
+// NewInt32 creates a new Int32.
 func NewInt32(i int32) *Int32 {
 	return &Int32{i}
 }
diff --git a/int64.go b/int64.go
index 2d6a1ff..41c7fef 100644
--- a/int64.go
+++ b/int64.go
@@ -25,10 +25,10 @@
 	"sync/atomic"
 )
 
-// Int64 is an atomic wrapper around an int64.
+// Int64 is an atomic wrapper around int64.
 type Int64 struct{ v int64 }
 
-// NewInt64 creates an Int64.
+// NewInt64 creates a new Int64.
 func NewInt64(i int64) *Int64 {
 	return &Int64{i}
 }
diff --git a/internal/gen-atomicint/main.go b/internal/gen-atomicint/main.go
new file mode 100644
index 0000000..39ca6c8
--- /dev/null
+++ b/internal/gen-atomicint/main.go
@@ -0,0 +1,201 @@
+// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// gen-atomicint generates an atomic wrapper around an integer type.
+//
+//  gen-atomicint -name Int32 -wrapped int32 -file out.go
+//
+// The generated wrapper will use the functions in the sync/atomic package
+// named after the generated type.
+package main
+
+import (
+	"bytes"
+	"errors"
+	"flag"
+	"fmt"
+	"go/format"
+	"io"
+	"log"
+	"os"
+	"text/template"
+)
+
+func main() {
+	log.SetFlags(0)
+	if err := run(os.Args[1:]); err != nil {
+		log.Fatalf("%+v", err)
+	}
+}
+
+func run(args []string) error {
+	var opts struct {
+		Name     string
+		Wrapped  string
+		File     string
+		Unsigned bool
+	}
+
+	flag := flag.NewFlagSet("gen-atomicint", flag.ContinueOnError)
+
+	flag.StringVar(&opts.Name, "name", "", "name of the generated type (e.g. Int32)")
+	flag.StringVar(&opts.Wrapped, "wrapped", "", "name of the wrapped type (e.g. int32)")
+	flag.StringVar(&opts.File, "file", "", "output file path (default: stdout)")
+	flag.BoolVar(&opts.Unsigned, "unsigned", false, "whether the type is unsigned")
+
+	if err := flag.Parse(args); err != nil {
+		return err
+	}
+
+	if len(opts.Name) == 0 || len(opts.Wrapped) == 0 {
+		return errors.New("flags -name and -wrapped are required")
+	}
+
+	var w io.Writer = os.Stdout
+	if file := opts.File; len(file) > 0 {
+		f, err := os.Create(file)
+		if err != nil {
+			return fmt.Errorf("create %q: %v", file, err)
+		}
+		defer f.Close()
+
+		w = f
+	}
+
+	data := struct {
+		Name     string
+		Wrapped  string
+		Unsigned bool
+	}{
+		Name:     opts.Name,
+		Wrapped:  opts.Wrapped,
+		Unsigned: opts.Unsigned,
+	}
+
+	var buff bytes.Buffer
+	if err := _tmpl.Execute(&buff, data); err != nil {
+		return fmt.Errorf("render template: %v", err)
+	}
+
+	bs, err := format.Source(buff.Bytes())
+	if err != nil {
+		return fmt.Errorf("reformat source: %v", err)
+	}
+
+	_, err = w.Write(bs)
+	return err
+}
+
+var _tmpl = template.Must(template.New("int.go").Parse(`// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+import (
+	"encoding/json"
+	"sync/atomic"
+)
+
+// {{ .Name }} is an atomic wrapper around {{ .Wrapped }}.
+type {{ .Name }} struct{ v {{ .Wrapped }} }
+
+// New{{ .Name }} creates a new {{ .Name }}.
+func New{{ .Name }}(i {{ .Wrapped }}) *{{ .Name }} {
+	return &{{ .Name }}{i}
+}
+
+// Load atomically loads the wrapped value.
+func (i *{{ .Name }}) Load() {{ .Wrapped }} {
+	return atomic.Load{{ .Name }}(&i.v)
+}
+
+// Add atomically adds to the wrapped {{ .Wrapped }} and returns the new value.
+func (i *{{ .Name }}) Add(n {{ .Wrapped }}) {{ .Wrapped }} {
+	return atomic.Add{{ .Name }}(&i.v, n)
+}
+
+// Sub atomically subtracts from the wrapped {{ .Wrapped }} and returns the new value.
+func (i *{{ .Name }}) Sub(n {{ .Wrapped }}) {{ .Wrapped }} {
+	return atomic.Add{{ .Name }}(&i.v,
+		{{- if .Unsigned -}}
+			^(n - 1)
+		{{- else -}}
+			-n
+		{{- end -}}
+	)
+}
+
+// Inc atomically increments the wrapped {{ .Wrapped }} and returns the new value.
+func (i *{{ .Name }}) Inc() {{ .Wrapped }} {
+	return i.Add(1)
+}
+
+// Dec atomically decrements the wrapped {{ .Wrapped }} and returns the new value.
+func (i *{{ .Name }}) Dec() {{ .Wrapped }} {
+	return i.Sub(1)
+}
+
+// CAS is an atomic compare-and-swap.
+func (i *{{ .Name }}) CAS(old, new {{ .Wrapped }}) bool {
+	return atomic.CompareAndSwap{{ .Name }}(&i.v, old, new)
+}
+
+// Store atomically stores the passed value.
+func (i *{{ .Name }}) Store(n {{ .Wrapped }}) {
+	atomic.Store{{ .Name }}(&i.v, n)
+}
+
+// Swap atomically swaps the wrapped {{ .Wrapped }} and returns the old value.
+func (i *{{ .Name }}) Swap(n {{ .Wrapped }}) {{ .Wrapped }} {
+	return atomic.Swap{{ .Name }}(&i.v, n)
+}
+
+// MarshalJSON encodes the wrapped {{ .Wrapped }} into JSON.
+func (i *{{ .Name }}) MarshalJSON() ([]byte, error) {
+	return json.Marshal(i.Load())
+}
+
+// UnmarshalJSON decodes JSON into the wrapped {{ .Wrapped }}.
+func (i *{{ .Name }}) UnmarshalJSON(b []byte) error {
+	var v {{ .Wrapped }}
+	if err := json.Unmarshal(b, &v); err != nil {
+		return err
+	}
+	i.Store(v)
+	return nil
+}
+`))
diff --git a/uint32.go b/uint32.go
index 69e8d77..aa5d5b1 100644
--- a/uint32.go
+++ b/uint32.go
@@ -25,10 +25,10 @@
 	"sync/atomic"
 )
 
-// Uint32 is an atomic wrapper around an uint32.
+// Uint32 is an atomic wrapper around uint32.
 type Uint32 struct{ v uint32 }
 
-// NewUint32 creates a Uint32.
+// NewUint32 creates a new Uint32.
 func NewUint32(i uint32) *Uint32 {
 	return &Uint32{i}
 }
@@ -53,7 +53,7 @@
 	return i.Add(1)
 }
 
-// Dec atomically decrements the wrapped int32 and returns the new value.
+// Dec atomically decrements the wrapped uint32 and returns the new value.
 func (i *Uint32) Dec() uint32 {
 	return i.Sub(1)
 }
diff --git a/uint64.go b/uint64.go
index 8cab42f..3771d96 100644
--- a/uint64.go
+++ b/uint64.go
@@ -25,10 +25,10 @@
 	"sync/atomic"
 )
 
-// Uint64 is an atomic wrapper around a uint64.
+// Uint64 is an atomic wrapper around uint64.
 type Uint64 struct{ v uint64 }
 
-// NewUint64 creates a Uint64.
+// NewUint64 creates a new Uint64.
 func NewUint64(i uint64) *Uint64 {
 	return &Uint64{i}
 }