Skip to content

Commit e50db3e

Browse files
authored
enhancement for enums (#1400)
* enhancement for enums: 1. explicit enum conversion; 2. implicit integer conversion; 3. keep the type of the right operand of a shift operation; 3. parse escape characters.
1 parent 8139731 commit e50db3e

8 files changed

Lines changed: 731 additions & 53 deletions

File tree

const.go

Lines changed: 551 additions & 0 deletions
Large diffs are not rendered by default.

enums_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,13 @@ func TestParseGlobalEnums(t *testing.T) {
2020
b, err := json.MarshalIndent(p.swagger, "", " ")
2121
assert.NoError(t, err)
2222
assert.Equal(t, string(expected), string(b))
23+
constsPath := "github.com/swaggo/swag/testdata/enums/consts"
24+
assert.Equal(t, 64, p.packages.packages[constsPath].ConstTable["uintSize"].Value)
25+
assert.Equal(t, int32(62), p.packages.packages[constsPath].ConstTable["maxBase"].Value)
26+
assert.Equal(t, 8, p.packages.packages[constsPath].ConstTable["shlByLen"].Value)
27+
assert.Equal(t, 255, p.packages.packages[constsPath].ConstTable["hexnum"].Value)
28+
assert.Equal(t, 15, p.packages.packages[constsPath].ConstTable["octnum"].Value)
29+
assert.Equal(t, `aa\nbb\u8888cc`, p.packages.packages[constsPath].ConstTable["nonescapestr"].Value)
30+
assert.Equal(t, "aa\nbb\u8888cc", p.packages.packages[constsPath].ConstTable["escapestr"].Value)
31+
assert.Equal(t, '\u8888', p.packages.packages[constsPath].ConstTable["escapechar"].Value)
2332
}

package.go

Lines changed: 69 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package swag
33
import (
44
"go/ast"
55
"go/token"
6+
"reflect"
67
"strconv"
78
)
89

@@ -31,6 +32,7 @@ type PackageDefinitions struct {
3132
type ConstVariableGlobalEvaluator interface {
3233
EvaluateConstValue(pkg *PackageDefinitions, cv *ConstVariable, recursiveStack map[string]struct{}) (interface{}, ast.Expr)
3334
EvaluateConstValueByName(file *ast.File, pkgPath, constVariableName string, recursiveStack map[string]struct{}) (interface{}, ast.Expr)
35+
FindTypeSpec(typeName string, file *ast.File) *TypeSpecDef
3436
}
3537

3638
// NewPackageDefinitions new a PackageDefinitions object
@@ -92,68 +94,89 @@ func (pkg *PackageDefinitions) evaluateConstValue(file *ast.File, iota int, expr
9294
case *ast.BasicLit:
9395
switch valueExpr.Kind {
9496
case token.INT:
95-
x, err := strconv.ParseInt(valueExpr.Value, 10, 64)
96-
if err != nil {
97-
return nil, nil
97+
// hexadecimal
98+
if len(valueExpr.Value) > 2 && valueExpr.Value[0] == '0' && valueExpr.Value[1] == 'x' {
99+
if x, err := strconv.ParseInt(valueExpr.Value[2:], 16, 64); err == nil {
100+
return int(x), nil
101+
} else if x, err := strconv.ParseUint(valueExpr.Value[2:], 16, 64); err == nil {
102+
return x, nil
103+
} else {
104+
panic(err)
105+
}
106+
}
107+
108+
//octet
109+
if len(valueExpr.Value) > 1 && valueExpr.Value[0] == '0' {
110+
if x, err := strconv.ParseInt(valueExpr.Value[1:], 8, 64); err == nil {
111+
return int(x), nil
112+
} else if x, err := strconv.ParseUint(valueExpr.Value[1:], 8, 64); err == nil {
113+
return x, nil
114+
} else {
115+
panic(err)
116+
}
117+
}
118+
119+
//a basic literal integer is int type in default, or must have an explicit converting type in front
120+
if x, err := strconv.ParseInt(valueExpr.Value, 10, 64); err == nil {
121+
return int(x), nil
122+
} else if x, err := strconv.ParseUint(valueExpr.Value, 10, 64); err == nil {
123+
return x, nil
124+
} else {
125+
panic(err)
98126
}
99-
return int(x), nil
100-
case token.STRING, token.CHAR:
101-
return valueExpr.Value[1 : len(valueExpr.Value)-1], nil
127+
case token.STRING:
128+
if valueExpr.Value[0] == '`' {
129+
return valueExpr.Value[1 : len(valueExpr.Value)-1], nil
130+
}
131+
return EvaluateEscapedString(valueExpr.Value[1 : len(valueExpr.Value)-1]), nil
132+
case token.CHAR:
133+
return EvaluateEscapedChar(valueExpr.Value[1 : len(valueExpr.Value)-1]), nil
102134
}
103135
case *ast.UnaryExpr:
104136
x, evalType := pkg.evaluateConstValue(file, iota, valueExpr.X, globalEvaluator, recursiveStack)
105137
if x == nil {
106-
return nil, nil
107-
}
108-
switch valueExpr.Op {
109-
case token.SUB:
110-
return -x.(int), evalType
111-
case token.XOR:
112-
return ^(x.(int)), evalType
138+
return x, evalType
113139
}
140+
return EvaluateUnary(x, valueExpr.Op, evalType)
114141
case *ast.BinaryExpr:
115142
x, evalTypex := pkg.evaluateConstValue(file, iota, valueExpr.X, globalEvaluator, recursiveStack)
116143
y, evalTypey := pkg.evaluateConstValue(file, iota, valueExpr.Y, globalEvaluator, recursiveStack)
117144
if x == nil || y == nil {
118145
return nil, nil
119146
}
120-
evalType := evalTypex
121-
if evalType == nil {
122-
evalType = evalTypey
123-
}
124-
switch valueExpr.Op {
125-
case token.ADD:
126-
if ix, ok := x.(int); ok {
127-
return ix + y.(int), evalType
128-
} else if sx, ok := x.(string); ok {
129-
return sx + y.(string), evalType
130-
}
131-
case token.SUB:
132-
return x.(int) - y.(int), evalType
133-
case token.MUL:
134-
return x.(int) * y.(int), evalType
135-
case token.QUO:
136-
return x.(int) / y.(int), evalType
137-
case token.REM:
138-
return x.(int) % y.(int), evalType
139-
case token.AND:
140-
return x.(int) & y.(int), evalType
141-
case token.OR:
142-
return x.(int) | y.(int), evalType
143-
case token.XOR:
144-
return x.(int) ^ y.(int), evalType
145-
case token.SHL:
146-
return x.(int) << y.(int), evalType
147-
case token.SHR:
148-
return x.(int) >> y.(int), evalType
149-
}
147+
return EvaluateBinary(x, y, valueExpr.Op, evalTypex, evalTypey)
150148
case *ast.ParenExpr:
151149
return pkg.evaluateConstValue(file, iota, valueExpr.X, globalEvaluator, recursiveStack)
152150
case *ast.CallExpr:
153151
//data conversion
154-
if ident, ok := valueExpr.Fun.(*ast.Ident); ok && len(valueExpr.Args) == 1 && IsGolangPrimitiveType(ident.Name) {
155-
arg, _ := pkg.evaluateConstValue(file, iota, valueExpr.Args[0], globalEvaluator, recursiveStack)
156-
return arg, nil
152+
if len(valueExpr.Args) != 1 {
153+
return nil, nil
154+
}
155+
arg := valueExpr.Args[0]
156+
if ident, ok := valueExpr.Fun.(*ast.Ident); ok {
157+
name := ident.Name
158+
if name == "uintptr" {
159+
name = "uint"
160+
}
161+
if IsGolangPrimitiveType(name) {
162+
value, _ := pkg.evaluateConstValue(file, iota, arg, globalEvaluator, recursiveStack)
163+
value = EvaluateDataConversion(value, name)
164+
return value, nil
165+
} else if name == "len" {
166+
value, _ := pkg.evaluateConstValue(file, iota, arg, globalEvaluator, recursiveStack)
167+
return reflect.ValueOf(value).Len(), nil
168+
}
169+
typeDef := globalEvaluator.FindTypeSpec(name, file)
170+
if typeDef == nil {
171+
return nil, nil
172+
}
173+
return arg, valueExpr.Fun
174+
} else if selector, ok := valueExpr.Fun.(*ast.SelectorExpr); ok {
175+
typeDef := globalEvaluator.FindTypeSpec(fullTypeName(selector.X.(*ast.Ident).Name, selector.Sel.Name), file)
176+
if typeDef == nil {
177+
return nil, nil
178+
}
179+
return arg, typeDef.TypeSpec.Type
157180
}
158181
}
159182
return nil, nil

testdata/enums/consts/const.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
11
package consts
22

33
const Base = 1
4+
5+
const uintSize = 32 << (^uint(uintptr(0)) >> 63)
6+
const maxBase = 10 + ('z' - 'a' + 1) + ('Z' - 'A' + 1)
7+
const shlByLen = 1 << len("aaa")
8+
const hexnum = 0xFF
9+
const octnum = 017
10+
const nonescapestr = `aa\nbb\u8888cc`
11+
const escapestr = "aa\nbb\u8888cc"
12+
const escapechar = '\u8888'

testdata/enums/main.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,4 @@ package main
1414

1515
// @BasePath /v2
1616
func main() {
17-
1817
}

testdata/enums/types/model.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ const (
1111
A Class = consts.Base + (iota+1-1)*2/2%100 - (1&1 | 1) + (2 ^ 2) // AAA
1212
B /* BBB */
1313
C
14-
D
15-
F = D + 1
14+
D = C + 1
15+
F = Class(5)
1616
//G is not enum
1717
G = H + 10
1818
//H is not enum
@@ -21,13 +21,15 @@ const (
2121
I = int(F + 2)
2222
)
2323

24+
const J = 1 << uint16(I)
25+
2426
type Mask int
2527

2628
const (
27-
Mask1 Mask = 2 << iota >> 1 // Mask1
28-
Mask2 /* Mask2 */
29-
Mask3 // Mask3
30-
Mask4 // Mask4
29+
Mask1 Mask = 0x02 << iota >> 1 // Mask1
30+
Mask2 /* Mask2 */
31+
Mask3 // Mask3
32+
Mask4 // Mask4
3133
)
3234

3335
type Type string
@@ -40,6 +42,13 @@ const (
4042
OtherUnknown = string(Other + Unknown)
4143
)
4244

45+
type Sex rune
46+
47+
const (
48+
Male Sex = 'M'
49+
Female = 'F'
50+
)
51+
4352
type Person struct {
4453
Name string
4554
Class Class

utils_go18.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//go:build go1.18
2+
// +build go1.18
3+
4+
package swag
5+
6+
import (
7+
"reflect"
8+
"unicode/utf8"
9+
)
10+
11+
// AppendUtf8Rune appends the UTF-8 encoding of r to the end of p and
12+
// returns the extended buffer. If the rune is out of range,
13+
// it appends the encoding of RuneError.
14+
func AppendUtf8Rune(p []byte, r rune) []byte {
15+
return utf8.AppendRune(p, r)
16+
}
17+
18+
// CanIntegerValue a wrapper of reflect.Value
19+
type CanIntegerValue struct {
20+
reflect.Value
21+
}
22+
23+
// CanInt reports whether Uint can be used without panicking.
24+
func (v CanIntegerValue) CanInt() bool {
25+
return v.Value.CanInt()
26+
}
27+
28+
// CanUint reports whether Uint can be used without panicking.
29+
func (v CanIntegerValue) CanUint() bool {
30+
return v.Value.CanUint()
31+
}

utils_other.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//go:build !go1.18
2+
// +build !go1.18
3+
4+
package swag
5+
6+
import (
7+
"reflect"
8+
"unicode/utf8"
9+
)
10+
11+
// AppendUtf8Rune appends the UTF-8 encoding of r to the end of p and
12+
// returns the extended buffer. If the rune is out of range,
13+
// it appends the encoding of RuneError.
14+
func AppendUtf8Rune(p []byte, r rune) []byte {
15+
length := utf8.RuneLen(rune(r))
16+
if length > 0 {
17+
utf8Slice := make([]byte, length)
18+
utf8.EncodeRune(utf8Slice, rune(r))
19+
p = append(p, utf8Slice...)
20+
}
21+
return p
22+
}
23+
24+
// CanIntegerValue a wrapper of reflect.Value
25+
type CanIntegerValue struct {
26+
reflect.Value
27+
}
28+
29+
// CanInt reports whether Uint can be used without panicking.
30+
func (v CanIntegerValue) CanInt() bool {
31+
switch v.Kind() {
32+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
33+
return true
34+
default:
35+
return false
36+
}
37+
}
38+
39+
// CanUint reports whether Uint can be used without panicking.
40+
func (v CanIntegerValue) CanUint() bool {
41+
switch v.Kind() {
42+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
43+
return true
44+
default:
45+
return false
46+
}
47+
}

0 commit comments

Comments
 (0)