Skip to content

Commit

Permalink
Fix type conversion for custom type definitions (#204)
Browse files Browse the repository at this point in the history
Type conversion for custom types (enums) was broken

e.g.
```go
type LoyaltyTier string
const (
	Bronze LoyaltyTier = "bronze"
)
```

Test plan: `go test -timeout 30s -run ^(TestStringComparsigon|TestNumericComparsigon)$`

Benchmark `convertToString`
before:
![Screenshot 2024-07-12 at 4 54 13 PM](https://github.com/user-attachments/assets/97c7e431-1188-4102-80ee-ec88e335acbb)
after: (note that the constant to string conversion was previously broken)
![Screenshot 2024-07-12 at 4 54 00 PM](https://github.com/user-attachments/assets/736e22d1-f4a8-4f6b-958b-2cc755c91dba)

Benchmark `getNumericValue`
before:
![Screenshot 2024-07-12 at 4 58 38 PM](https://github.com/user-attachments/assets/d956c7be-7f36-4cd2-b970-2466bddc91b2)
after:
![Screenshot 2024-07-12 at 4 58 29 PM](https://github.com/user-attachments/assets/85ee00c2-becf-4070-a595-642a9af0f60f)
  • Loading branch information
kenny-statsig authored Aug 20, 2024
1 parent 9f37bc1 commit 3c50265
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 26 deletions.
54 changes: 28 additions & 26 deletions evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -748,21 +748,19 @@ func removeEmptyStrings(s []string) []string {
}

func getNumericValue(a interface{}) (float64, bool) {
switch a := a.(type) {
case int:
return float64(a), true
case int32:
return float64(a), true
case int64:
return float64(a), true
case uint64:
return float64(a), true
case float32:
return float64(a), true
case float64:
return a, true
case string:
f, err := strconv.ParseFloat(a, 64)
if a == nil {
return 0, false
}
aVal := reflect.ValueOf(a)
switch reflect.TypeOf(a).Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return float64(aVal.Int()), true
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return float64(aVal.Uint()), true
case reflect.Float32, reflect.Float64:
return float64(aVal.Float()), true
case reflect.String:
f, err := strconv.ParseFloat(aVal.String(), 64)
if err == nil {
return f, true
}
Expand All @@ -779,20 +777,24 @@ func castToString(a interface{}) string {
}

func convertToString(a interface{}) string {
if a == nil {
return ""
}
if asString, ok := a.(string); ok {
return asString
}
if asInt, ok := a.(int); ok {
return strconv.Itoa(asInt)
}
if asInt64, ok := a.(int64); ok {
return strconv.FormatInt(asInt64, 10)
}
if asFloat, ok := a.(float64); ok {
return strconv.FormatFloat(asFloat, 'f', -1, 64)
}
if asBool, ok := a.(bool); ok {
return strconv.FormatBool(asBool)
aVal := reflect.ValueOf(a)
switch aVal.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(aVal.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.FormatUint(aVal.Uint(), 10)
case reflect.Float32, reflect.Float64:
return strconv.FormatFloat(aVal.Float(), 'f', -1, 64)
case reflect.Bool:
return strconv.FormatBool(aVal.Bool())
case reflect.String:
return fmt.Sprintf("%v", a)
}
return ""
}
Expand Down
74 changes: 74 additions & 0 deletions evaluator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package statsig

import "testing"

func TestStringComparsigon(t *testing.T) {
eq := func(s1, s2 string) bool { return s1 == s2 }

if !compareStrings("a", "a", true, eq) {
t.Error("Expected string equality check to pass")
}
if !compareStrings("a", "A", true, eq) {
t.Error("Expected case-insensitive string equality check to pass")
}
if !compareStrings(true, "true", true, eq) {
t.Error("Expected boolean to string equality check to pass")
}
var numInt int = 1
if !compareStrings(numInt, "1", true, eq) {
t.Error("Expected integer to string equality check to pass")
}

type StringDefinition string
const (
A1 StringDefinition = "a"
)
if !compareStrings(A1, "a", true, eq) {
t.Error("Expected string custom definition equality check to pass")
}

type StringAlias = string
const (
A2 StringAlias = "a"
)
if !compareStrings(A2, "a", true, eq) {
t.Error("Expected string alias equality check to pass")
}
}

func TestNumericComparsigon(t *testing.T) {
eq := func(x, y float64) bool { return x == y }

var numInt int = 1
if !compareNumbers(numInt, 1, eq) {
t.Error("Expected int equality check to pass")
}
var numUInt uint = 1
if !compareNumbers(numUInt, 1, eq) {
t.Error("Expected uint equality check to pass")
}
var numFloat32 float32 = 1
if !compareNumbers(numFloat32, 1, eq) {
t.Error("Expected float32 equality check to pass")
}
var numFloat64 float64 = 1
if !compareNumbers(numFloat64, 1, eq) {
t.Error("Expected float64 equality check to pass")
}

type IntDefinition int
const (
Int1 IntDefinition = 1
)
if !compareNumbers(Int1, 1, eq) {
t.Error("Expected int custom definition equality check to pass")
}

type IntAlias = int
const (
Int2 IntAlias = 1
)
if !compareNumbers(Int2, 1, eq) {
t.Error("Expected int alias equality check to pass")
}
}

0 comments on commit 3c50265

Please sign in to comment.