Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go: ["1.18.x", "1.22.x", "1.23.x"]
go: ["1.18.x", "1.23.x", "1.25.x"]
include:
- go: 1.23.x
- go: 1.18.x
latest: true

steps:
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go }}

- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Load cached dependencies
uses: actions/cache@v4
Expand All @@ -35,11 +35,11 @@ jobs:
restore-keys: |
${{ runner.os }}-go-

- name: Download Dependencies
run: make prepare

- name: Lint
run: make lint
uses: golangci/golangci-lint-action@v8
with:
version: v2.4.0
args: --timeout=3m -v

- name: Test
run: make cover
Expand Down
87 changes: 87 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
version: "2"
linters:
enable:
- bodyclose
- contextcheck
- copyloopvar
- dogsled
- err113
- errname
- errorlint
- exhaustive
- forbidigo
- forcetypeassert
- funlen
- gocognit
- goconst
- gocritic
- gocyclo
- gosec
- lll
- misspell
- mnd
- nakedret
- nestif
- nilerr
- rowserrcheck
- staticcheck
- unconvert
- unparam
- whitespace
settings:
funlen:
lines: 120
statements: 80
gocognit:
min-complexity: 30
gocyclo:
min-complexity: 30
lll:
line-length: 120
misspell:
locale: US
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- contextcheck
- err113
- forcetypeassert
- funlen
- gocognit
- gocyclo
- gosec
- mnd
- staticcheck
- unused
- wrapcheck
path: _test\.go
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gci
- gofmt
- goimports
settings:
gci:
sections:
- standard
- default
- prefix(github.com/tiendc/go-deepcopy)
goimports:
local-prefixes:
- github.com/golangci/golangci-lint
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
73 changes: 0 additions & 73 deletions .golangci.yml

This file was deleted.

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
all: lint test

prepare:
@curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.60.3
@curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.4.0

build:
@go build -v ./...
Expand Down
35 changes: 33 additions & 2 deletions build_copier.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package deepcopy
import (
"fmt"
"reflect"
"strings"
"sync"
)

Expand Down Expand Up @@ -171,8 +172,14 @@ func buildCopier(ctx *Context, dstType, srcType reflect.Type) (copier copier, er
//nolint:nestif
if srcKind == reflect.Struct {
if dstKind == reflect.Struct {
// At this point, cachedCopier should be `nil`.
// If it's non-nil, seems like a circular reference occurs, use an inline copier.
// Build a special copier for Go standard types such as time.Time, unique.Handle
copier = buildCopierForStandardStructs(dstType, srcType)
if copier != nil {
goto OnComplete
}

// At this point, cached copier should not exist in the cache map.
// If it exists, seems like a circular reference occurs, use an inline copier.
if cachedCopierFound {
return &inlineCopier{ctx: ctx, dstType: dstType, srcType: srcType}, nil
}
Expand Down Expand Up @@ -226,6 +233,30 @@ OnNonCopyable:
return nil, fmt.Errorf("%w: %v -> %v", ErrTypeNonCopyable, srcType, dstType)
}

func buildCopierForStandardStructs(dstType, srcType reflect.Type) copier {
switch srcType.PkgPath() {
// When copy time.Time -> time.Time or derived type
case "time":
if dstType == srcType {
return defaultDirectCopier
}
if dstType.ConvertibleTo(srcType) {
return defaultConvCopier
}
// When copy unique.Handle[T] -> unique.Handle[T] or derived type
case "unique":
if strings.HasPrefix(srcType.Name(), "Handle[") {
if dstType == srcType {
return defaultDirectCopier
}
if dstType.ConvertibleTo(srcType) {
return defaultConvCopier
}
}
}
return nil
}

func setCachedCopier(ctx *Context, cacheKey *cacheKey, cp copier) {
ctx.mu.Lock()
ctx.copierCacheMap[*cacheKey] = cp
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module github.com/tiendc/go-deepcopy

go 1.18

require github.com/stretchr/testify v1.9.0
require github.com/stretchr/testify v1.11.1

require (
github.com/davecgh/go-spew v1.1.1 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
62 changes: 62 additions & 0 deletions struct_copier_go1.23_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//go:build go1.23

package deepcopy

import (
"testing"
"unique"

"github.com/stretchr/testify/assert"
)

func Test_Copy_struct_on_standard_types_go1_23(t *testing.T) {
t.Run("#1: Copy unique.Handle[string]", func(t *testing.T) {
s := unique.Make("hello")
var d unique.Handle[string]
err := Copy(&d, s)
assert.Nil(t, err)
assert.Equal(t, d, s)
assert.Equal(t, d.Value(), s.Value())

// unique.Handle[T] as struct fields
type A struct {
I int
}
type S struct {
H unique.Handle[A]
}
type D struct {
H unique.Handle[A]
}
s2 := S{H: unique.Make(A{I: 111})}
var d2 D
err = Copy(&d2, s2)
assert.Nil(t, err)
assert.Equal(t, d2.H, s2.H)
assert.Equal(t, d2.H.Value(), s2.H.Value())
})

t.Run("#2: Copy unique.Handle[string] to derived type", func(t *testing.T) {
type T unique.Handle[string]
s := unique.Make("hello")
var d T
err := Copy(&d, s)
assert.Nil(t, err)
assert.Equal(t, unique.Handle[string](d), s)
assert.Equal(t, unique.Handle[string](d).Value(), s.Value())

// unique.Handle[T] as struct fields
type S struct {
H unique.Handle[string]
}
type D struct {
H T
}
s2 := S{H: unique.Make("hello")}
var d2 D
err = Copy(&d2, s2)
assert.Nil(t, err)
assert.Equal(t, unique.Handle[string](d2.H), s2.H)
assert.Equal(t, unique.Handle[string](d2.H).Value(), s2.H.Value())
})
}
Loading
Loading