Repository: VKCOM/noverify
Pull request #595 (2020-07-17 12:10:29)

Improved storage of the value of constants
The way of storing values for constants has been changed. Now interface{} is used for this, which avoids unnecessary conversions from a string to a certain type and back.

Since the data is not stored in a string, methods for decoding and encoding and tests for them were written for caching.

Solve #594 


Patch:
diff --git a/src/linter/block.go b/src/linter/block.go
index 8130a31..1365338 100644
--- a/src/linter/block.go
+++ b/src/linter/block.go
@@ -4,7 +4,6 @@ import (
 	"fmt"
 	"math"
 	"regexp"
-	"strconv"
 	"strings"
 
 	"github.com/VKCOM/noverify/src/meta"
@@ -1369,15 +1368,22 @@ func (b *BlockWalker) handleArrayItems(arr node.Node, items []*expr.ArrayItem) b
 				continue
 			}
 
-			key = info.Value.Value
+			value := info.Value.Value
 
-			if info.Value.Type == meta.Float {
-				val, err := strconv.ParseFloat(key, 64)
-				if err != nil {
+			switch info.Value.Type {
+			case meta.Float:
+				val, ok := value.(float64)
+				if !ok {
 					continue
 				}
 				value := int64(val)
 				key = fmt.Sprint(value)
+
+			case meta.Integer:
+				key = fmt.Sprint(value)
+
+			case meta.String:
+				key = value.(string)
 			}
 
 			constKey = true
diff --git a/src/meta/metainfo.go b/src/meta/metainfo.go
index 130889c..d0d3665 100644
--- a/src/meta/metainfo.go
+++ b/src/meta/metainfo.go
@@ -380,23 +380,81 @@ const (
 
 type ConstantValue struct {
 	Type  ConstantValueType
-	Value string
+	Value interface{}
 }
 
-func (cv ConstantValue) String() string {
-	if cv.Type == Undefined {
-		return "Undefined type"
+func (c ConstantValue) GobEncode() ([]byte, error) {
+	switch c.Type {
+	case Float:
+		val, ok := c.Value.(float64)
+		if !ok {
+			return nil, fmt.Errorf("corrupted float")
+		}
+		str := fmt.Sprintf("%c%f", c.Type, val)
+		return []byte(str), nil
+	case Integer:
+		val, ok := c.Value.(int64)
+		if !ok {
+			return nil, fmt.Errorf("corrupted integer")
+		}
+		str := fmt.Sprintf("%c%d", c.Type, val)
+		return []byte(str), nil
+	case String:
+		val, ok := c.Value.(string)
+		if !ok {
+			return nil, fmt.Errorf("corrupted string")
+		}
+		str := fmt.Sprintf("%c%s", c.Type, val)
+		return []byte(str), nil
+	}
+
+	return nil, fmt.Errorf("unhandeled type")
+}
+
+func (c *ConstantValue) GobDecode(buf []byte) error {
+	if len(buf) < 1 {
+		return fmt.Errorf("data corrupted")
+	}
+
+	tp := ConstantValueType(buf[0])
+	buf = buf[1:]
+	val := string(buf)
+
+	switch tp {
+	case Float:
+		value, err := strconv.ParseFloat(val, 64)
+		if err != nil {
+			return fmt.Errorf("invalid float")
+		}
+		c.Value = value
+	case Integer:
+		value, err := strconv.ParseInt(val, 10, 64)
+		if err != nil {
+			return fmt.Errorf("invalid integer")
+		}
+		c.Value = value
+	case String:
+		c.Value = val
 	}
 
-	return fmt.Sprintf("%d: %s", cv.Type, cv.Value)
+	c.Type = tp
+
+	return nil
+}
+
+func (c ConstantValue) String() string {
+	if c.Type == Undefined {
+		return "Undefined type"
+	}
+	return fmt.Sprintf("%d: %s", c.Type, c.Value)
 }
 
-func (cv ConstantValue) IsEqual(v ConstantValue) bool {
-	if v.Type == Undefined || cv.Type == Undefined {
+func (c ConstantValue) IsEqual(v ConstantValue) bool {
+	if v.Type == Undefined || c.Type == Undefined {
 		return false
 	}
 
-	return cv.Value == v.Value
+	return c.Value == v.Value
 }
 
 func NewConstantValueFromString(value string) ConstantValue {
@@ -408,11 +466,11 @@ func NewConstantValueFromString(value string) ConstantValue {
 }
 
 func NewConstantValueFromFloat(value float64) ConstantValue {
-	return ConstantValue{Value: fmt.Sprintf("%f", value), Type: Float}
+	return ConstantValue{Value: value, Type: Float}
 }
 
 func NewConstantValueFromInt(value int64) ConstantValue {
-	return ConstantValue{Value: fmt.Sprintf("%d", value), Type: Integer}
+	return ConstantValue{Value: value, Type: Integer}
 }
 
 func NewUndefinedConstantValue() ConstantValue {


Test patch:
diff --git a/src/linter/cache_test.go b/src/linter/cache_test.go
index fd0bb38..3477892 100644
--- a/src/linter/cache_test.go
+++ b/src/linter/cache_test.go
@@ -111,7 +111,7 @@ main();
 		//
 		// If cache encoding changes, there is a very high chance that
 		// encoded data lengh will change as well.
-		wantLen := 4109
+		wantLen := 4078
 		haveLen := buf.Len()
 		if haveLen != wantLen {
 			t.Errorf("cache len mismatch:\nhave: %d\nwant: %d", haveLen, wantLen)
@@ -120,7 +120,7 @@ main();
 		// 2. Check cache "strings" hash.
 		//
 		// It catches new fields in cached types, field renames and encoding of additional named attributes.
-		wantStrings := "f5c3f410fd821309de4eaf6bc68ca6aee07dcb904edeaa7b8cb6798291103f9036dbc5ffbff87dc01f04d26f0483a623caf5511f60d4b66a60952870a516c11c"
+		wantStrings := "5229b096d5a9f0ed73cea28dce9677760e620ed5fd33ccda12355159735e181b09ef152b4d518af886f13b1c7bebd2f1ca274b5485b91d0f23813d05ee646382"
 		haveStrings := collectCacheStrings(buf.String())
 		if haveStrings != wantStrings {
 			t.Errorf("cache strings mismatch:\nhave: %q\nwant: %q", haveStrings, wantStrings)
diff --git a/src/meta/encoding_test.go b/src/meta/encoding_test.go
index 65fafd2..418e70e 100644
--- a/src/meta/encoding_test.go
+++ b/src/meta/encoding_test.go
@@ -94,3 +94,34 @@ func TestTypeEncoding(t *testing.T) {
 		}
 	}
 }
+
+func TestConstantValueDecodeEncode(t *testing.T) {
+	testCases := []ConstantValue{
+		{Type: String, Value: "world"},
+		{Type: Integer, Value: int64(5)},
+		{Type: Float, Value: 5.56},
+		{Type: String, Value: "hello"},
+		{Type: Float, Value: 124.67},
+		{Type: Integer, Value: int64(50000000)},
+	}
+
+	for _, testCase := range testCases {
+		// encode this
+		encoded, err := testCase.GobEncode()
+		if err != nil {
+			t.Errorf("unexpected error \"%s\"", err)
+		}
+
+		// decode this
+		decoded := ConstantValue{}
+		err = decoded.GobDecode(encoded)
+		if err != nil {
+			t.Errorf("unexpected error \"%s\"", err)
+		}
+
+		// compare
+		if decoded.Type != testCase.Type || decoded.Value != testCase.Value {
+			t.Error("error decode, objects not equal")
+		}
+	}
+}