Skip to content

Commit

Permalink
Refactor types table
Browse files Browse the repository at this point in the history
  • Loading branch information
antonmedv committed Jun 2, 2024
1 parent efa006a commit 9b6b157
Show file tree
Hide file tree
Showing 13 changed files with 334 additions and 277 deletions.
31 changes: 12 additions & 19 deletions checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,8 @@ func (v *checker) IdentifierNode(node *ast.IdentifierNode) Nature {

// ident method returns type of environment variable, builtin or function.
func (v *checker) ident(node ast.Node, name string, strict, builtins bool) Nature {
if t, ok := v.config.Types[name]; ok {
if t.Ambiguous {
return v.error(node, "ambiguous identifier %v", name)
}
if t.Type == nil {
return nilNature
}
return Nature{Type: t.Type, Method: t.Method}
if nt, ok := v.config.Env.Get(name); ok {
return nt
}
if builtins {
if fn, ok := v.config.Functions[name]; ok {
Expand All @@ -219,9 +213,6 @@ func (v *checker) ident(node ast.Node, name string, strict, builtins bool) Natur
if v.config.Strict && strict {
return v.error(node, "unknown name %v", name)
}
if v.config.DefaultType != nil {
return Nature{Type: v.config.DefaultType}
}
return unknown
}

Expand Down Expand Up @@ -421,13 +412,13 @@ func (v *checker) BinaryNode(node *ast.BinaryNode) Nature {
if isInteger(l) && isInteger(r) {
return Nature{
Type: arrayType,
SubType: Array{Of: integerNature},
SubType: Array{Elem: integerNature},
}
}
if or(l, r, isInteger) {
return Nature{
Type: arrayType,
SubType: Array{Of: integerNature},
SubType: Array{Elem: integerNature},
}
}

Expand Down Expand Up @@ -512,7 +503,7 @@ func (v *checker) MemberNode(node *ast.MemberNode) Nature {
case reflect.Struct:
if name, ok := node.Property.(*ast.StringNode); ok {
propertyName := name.Value
if field, ok := fetchField(base, propertyName); ok {
if field, ok := base.FieldByName(propertyName); ok {
return Nature{Type: field.Type}
}
if node.Method {
Expand Down Expand Up @@ -661,7 +652,7 @@ func (v *checker) BuiltinNode(node *ast.BuiltinNode) Nature {
}
return Nature{
Type: arrayType,
SubType: Array{Of: collection.Elem()},
SubType: Array{Elem: collection.Elem()},
}
}
return v.error(node.Arguments[1], "predicate should has one input and one output param")
Expand All @@ -682,7 +673,7 @@ func (v *checker) BuiltinNode(node *ast.BuiltinNode) Nature {

return Nature{
Type: arrayType,
SubType: Array{Of: closure.Out(0)},
SubType: Array{Elem: closure.Out(0)},
}
}
return v.error(node.Arguments[1], "predicate should has one input and one output param")
Expand Down Expand Up @@ -879,7 +870,9 @@ func (v *checker) checkBuiltinGet(node *ast.BuiltinNode) Nature {

if id, ok := node.Arguments[0].(*ast.IdentifierNode); ok && id.Value == "$env" {
if s, ok := node.Arguments[1].(*ast.StringNode); ok {
return Nature{Type: v.config.Types[s.Value].Type}
if nt, ok := v.config.Env.Get(s.Value); ok {
return nt
}
}
return unknown
}
Expand Down Expand Up @@ -1145,7 +1138,7 @@ func (v *checker) PointerNode(node *ast.PointerNode) Nature {
}

func (v *checker) VariableDeclaratorNode(node *ast.VariableDeclaratorNode) Nature {
if _, ok := v.config.Types[node.Name]; ok {
if _, ok := v.config.Env.Get(node.Name); ok {
return v.error(node, "cannot redeclare %v", node.Name)
}
if _, ok := v.config.Functions[node.Name]; ok {
Expand Down Expand Up @@ -1212,7 +1205,7 @@ func (v *checker) ArrayNode(node *ast.ArrayNode) Nature {
if allElementsAreSameType {
return Nature{
Type: arrayNature.Type,
SubType: Array{Of: prev},
SubType: Array{Elem: prev},
}
}
return arrayNature
Expand Down
39 changes: 20 additions & 19 deletions checker/checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -758,25 +758,26 @@ func TestCheck_TaggedFieldName(t *testing.T) {
assert.NoError(t, err)
}

func TestCheck_Ambiguous(t *testing.T) {
type A struct {
Ambiguous bool
}
type B struct {
Ambiguous int
}
type Env struct {
A
B
}

tree, err := parser.Parse(`Ambiguous == 1`)
require.NoError(t, err)

_, err = checker.Check(tree, conf.New(Env{}))
assert.Error(t, err)
assert.Contains(t, err.Error(), "ambiguous identifier Ambiguous")
}
// Let's do the same thing Go does and allow ambiguous identifiers - return first one found.
//func TestCheck_Ambiguous(t *testing.T) {
// type A struct {
// Ambiguous bool
// }
// type B struct {
// Ambiguous int
// }
// type Env struct {
// A
// B
// }
//
// tree, err := parser.Parse(`Ambiguous == 1`)
// require.NoError(t, err)
//
// _, err = checker.Check(tree, conf.New(Env{}))
// assert.Error(t, err)
// assert.Contains(t, err.Error(), "ambiguous identifier Ambiguous")
//}

func TestCheck_NoConfig(t *testing.T) {
tree, err := parser.Parse(`any`)
Expand Down
22 changes: 13 additions & 9 deletions checker/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,40 @@ import (
"reflect"

"github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/conf"
. "github.com/expr-lang/expr/checker/nature"
"github.com/expr-lang/expr/vm"
)

func FieldIndex(types conf.TypesTable, node ast.Node) (bool, []int, string) {
func FieldIndex(env Nature, node ast.Node) (bool, []int, string) {
switch n := node.(type) {
case *ast.IdentifierNode:
if t, ok := types[n.Value]; ok && len(t.FieldIndex) > 0 {
return true, t.FieldIndex, n.Value
if env.Kind() == reflect.Struct {
if field, ok := env.Get(n.Value); ok && len(field.FieldIndex) > 0 {
return true, field.FieldIndex, n.Value
}
}
case *ast.MemberNode:
base := n.Node.Nature()
base = base.Deref()
if base.Kind() == reflect.Struct {
if prop, ok := n.Property.(*ast.StringNode); ok {
name := prop.Value
if field, ok := fetchField(base, name); ok {
return true, field.Index, name
if field, ok := base.FieldByName(name); ok {
return true, field.FieldIndex, name
}
}
}
}
return false, nil, ""
}

func MethodIndex(types conf.TypesTable, node ast.Node) (bool, int, string) {
func MethodIndex(env Nature, node ast.Node) (bool, int, string) {
switch n := node.(type) {
case *ast.IdentifierNode:
if t, ok := types[n.Value]; ok {
return t.Method, t.MethodIndex, n.Value
if env.Kind() == reflect.Struct {
if m, ok := env.Get(n.Value); ok {
return m.Method, m.MethodIndex, n.Value
}
}
case *ast.MemberNode:
if name, ok := n.Property.(*ast.StringNode); ok {
Expand Down
117 changes: 97 additions & 20 deletions checker/nature/nature.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ var (
)

type Nature struct {
Type reflect.Type
SubType SubType
Func *builtin.Function
Method bool
Type reflect.Type
SubType SubType
Func *builtin.Function
Method bool
MethodIndex int
FieldIndex []int
}

func (n Nature) String() string {
Expand Down Expand Up @@ -55,7 +57,7 @@ func (n Nature) Elem() Nature {
return Nature{Type: n.Type.Elem()}
case reflect.Array, reflect.Slice:
if array, ok := n.SubType.(Array); ok {
return array.Of
return array.Elem
}
return Nature{Type: n.Type.Elem()}
}
Expand Down Expand Up @@ -88,24 +90,14 @@ func (n Nature) MethodByName(name string) (Nature, bool) {
// the same interface.
return Nature{Type: method.Type}, true
} else {
return Nature{Type: method.Type, Method: true}, true
return Nature{
Type: method.Type,
Method: true,
MethodIndex: method.Index,
}, true
}
}

func (n Nature) NumField() int {
if n.Type == nil {
return 0
}
return n.Type.NumField()
}

func (n Nature) Field(i int) reflect.StructField {
if n.Type == nil {
return reflect.StructField{}
}
return n.Type.Field(i)
}

func (n Nature) NumIn() int {
if n.Type == nil {
return 0
Expand Down Expand Up @@ -140,3 +132,88 @@ func (n Nature) IsVariadic() bool {
}
return n.Type.IsVariadic()
}

func (n Nature) FieldByName(name string) (Nature, bool) {
if n.Type == nil {
return unknown, false
}
field, ok := fetchField(n.Type, name)
return Nature{Type: field.Type, FieldIndex: field.Index}, ok
}

func (n Nature) IsFastMap() bool {
if n.Type == nil {
return false
}
if n.Type.Kind() == reflect.Map &&
n.Type.Key().Kind() == reflect.String &&
n.Type.Elem().Kind() == reflect.Interface {
return true
}
return false
}

func (n Nature) Get(name string) (Nature, bool) {
if n.Type == nil {
return unknown, false
}

if m, ok := n.MethodByName(name); ok {
return m, true
}

t := deref.Type(n.Type)

switch t.Kind() {
case reflect.Struct:
if f, ok := fetchField(t, name); ok {
return Nature{
Type: f.Type,
FieldIndex: f.Index,
}, true
}
case reflect.Map:
if f, ok := n.SubType.Get(name); ok {
return f, true
}
}
return unknown, false
}

func (n Nature) List() map[string]Nature {
table := make(map[string]Nature)

if n.Type == nil {
return table
}

for i := 0; i < n.Type.NumMethod(); i++ {
method := n.Type.Method(i)
table[method.Name] = Nature{
Type: method.Type,
Method: true,
MethodIndex: method.Index,
}
}

switch n.Type.Kind() {
case reflect.Struct:
for name, nt := range fields(n.Type) {
if _, ok := table[name]; ok {
continue
}
table[name] = nt
}

case reflect.Map:
v := reflect.ValueOf(n.SubType)
for _, key := range v.MapKeys() {
value := v.MapIndex(key)
if key.Kind() == reflect.String && value.IsValid() && value.CanInterface() {
table[key.String()] = Nature{Type: reflect.TypeOf(value.Interface())}
}
}
}

return table
}
Loading

0 comments on commit 9b6b157

Please sign in to comment.