mirror of
https://github.com/hanzoai/expr.git
synced 2026-09-24 16:58:58 +00:00
Improve Performance of the Type System (#824)
* export the checker and ensure no methods or fields are exported * make Checker reusable * add PatchAndCheck method to Checker, and reuse it in visitors * avoid unnecessary map and consolidate structs * make all Nature methods use pointer-receiver * added cache for type and nature information * optimize type and nature checks * more aggressively cache some partial results and add cheap checks * cache reused values * reduce copying in Nature by breaking down optional fields * enforce use of cache * improve ergonomics of FromType and NatureOf * improve struct method fetching * make cache private to Nature * first iteration improving struct reflect caching * cleaned up struct fields data * improve caching of functions data * simplify checker code * simplify and remove dead code * add checker benchmarks * address PR comments and minor improvements * rename from SetCache to Bind * make the benchmarks more fair * fix benchmark option not being applied * move Cache to be argument instead of field * merge FuncData into Optional * rename Optional to TypeData * simplify struct logic * cache builtin numeric types --------- Co-authored-by: Anton Medvedev <[email protected]>
This commit is contained in:
committed by
GitHub
co-authored by
Anton Medvedev
parent
fd562c5c2a
commit
a60d994e25
+1
-1
@@ -66,7 +66,7 @@ func (n *base) Type() reflect.Type {
|
||||
|
||||
// SetType sets the type of the node.
|
||||
func (n *base) SetType(t reflect.Type) {
|
||||
n.nature.Type = t
|
||||
n.nature = nature.FromType(t)
|
||||
}
|
||||
|
||||
// NilNode represents nil.
|
||||
|
||||
+497
-467
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,114 @@
|
||||
package checker_test
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/expr-lang/expr"
|
||||
"github.com/expr-lang/expr/ast"
|
||||
"github.com/expr-lang/expr/checker"
|
||||
"github.com/expr-lang/expr/checker/nature"
|
||||
"github.com/expr-lang/expr/conf"
|
||||
"github.com/expr-lang/expr/parser"
|
||||
)
|
||||
|
||||
func BenchmarkChecker(b *testing.B) {
|
||||
cases := []struct {
|
||||
name, input string
|
||||
}{
|
||||
{"function calls", `
|
||||
func(
|
||||
func(
|
||||
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
||||
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
||||
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
||||
),
|
||||
func(
|
||||
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
||||
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
||||
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
||||
),
|
||||
func(
|
||||
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
||||
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
||||
func(func(a, 'a', 1, nil), func(a, 'a', 1, nil), func(a, 'a', 1, nil)),
|
||||
)
|
||||
)
|
||||
`},
|
||||
{"unary and binary operations", `
|
||||
!b && !b || !b == !b && !b != !b || 1 < 1.0 && 0.1 > 1 || 0 <= 1.0 && 0.1 >= 1 &&
|
||||
!b && !b || !b == !b && !b != !b || 1 < 1.0 && 0.1 > 1 || 0 <= 1.0 && 0.1 >= 1 &&
|
||||
!b && !b || !b == !b && !b != !b || 1 < 1.0 && 0.1 > 1 || 0 <= 1.0 && 0.1 >= 1 &&
|
||||
!b && !b || !b == !b && !b != !b || 1 < 1.0 && 0.1 > 1 || 0 <= 1.0 && 0.1 >= 1 &&
|
||||
!b && !b || !b == !b && !b != !b || 1 < 1.0 && 0.1 > 1 || 0 <= 1.0 && 0.1 >= 1 &&
|
||||
!b && !b || !b == !b && !b != !b || 1 < 1.0 && 0.1 > 1 || 0 <= 1.0 && 0.1 >= 1
|
||||
`},
|
||||
{"deep struct access", `
|
||||
a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.
|
||||
a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.
|
||||
a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.
|
||||
a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a
|
||||
`},
|
||||
}
|
||||
|
||||
f := func(params ...any) (any, error) { return nil, nil }
|
||||
env := map[string]any{
|
||||
"a": new(recursive),
|
||||
"b": true,
|
||||
"func": f,
|
||||
}
|
||||
config := conf.New(env)
|
||||
expr.Function("func", f, f)(config)
|
||||
expr.ConstExpr("func")(config)
|
||||
|
||||
for _, c := range cases {
|
||||
batchSize := 100_000
|
||||
if batchSize > b.N {
|
||||
batchSize = b.N
|
||||
}
|
||||
trees := make([]*parser.Tree, 0, batchSize)
|
||||
for i := 0; i < batchSize; i++ {
|
||||
tree, err := parser.ParseWithConfig(c.input, config)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
trees = append(trees, tree)
|
||||
}
|
||||
runtime.GC() // try to cleanup the mess from the initialization
|
||||
|
||||
b.Run("name="+c.name, func(b *testing.B) {
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
j := i
|
||||
if j < 0 || j >= len(trees) {
|
||||
b.StopTimer()
|
||||
invalidateTrees(trees...)
|
||||
j = 0
|
||||
b.StartTimer()
|
||||
}
|
||||
|
||||
_, err = checker.Check(trees[j], config)
|
||||
}
|
||||
b.StopTimer()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type visitorFunc func(*ast.Node)
|
||||
|
||||
func (f visitorFunc) Visit(node *ast.Node) { f(node) }
|
||||
|
||||
func invalidateTrees(trees ...*parser.Tree) {
|
||||
for _, tree := range trees {
|
||||
ast.Walk(&tree.Node, visitorFunc(func(node *ast.Node) {
|
||||
(*node).SetNature(nature.Nature{})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
type recursive struct {
|
||||
Inner *recursive `expr:"a"`
|
||||
}
|
||||
+15
-8
@@ -136,6 +136,7 @@ func TestCheck(t *testing.T) {
|
||||
{"(Embed).EmbedPointerEmbedInt > 0"},
|
||||
}
|
||||
|
||||
c := new(checker.Checker)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
var err error
|
||||
@@ -145,7 +146,7 @@ func TestCheck(t *testing.T) {
|
||||
config := conf.New(mock.Env{})
|
||||
expr.AsBool()(config)
|
||||
|
||||
_, err = checker.Check(tree, config)
|
||||
_, err = c.Check(tree, config)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
@@ -682,12 +683,13 @@ invalid operation: + (mismatched types int and bool) (1:6)
|
||||
},
|
||||
}
|
||||
|
||||
c := new(checker.Checker)
|
||||
for _, tt := range errorTests {
|
||||
t.Run(tt.code, func(t *testing.T) {
|
||||
tree, err := parser.Parse(tt.code)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = checker.Check(tree, conf.New(mock.Env{}))
|
||||
_, err = c.Check(tree, conf.New(mock.Env{}))
|
||||
if err == nil {
|
||||
err = fmt.Errorf("<nil>")
|
||||
}
|
||||
@@ -839,6 +841,7 @@ func TestCheck_TypeWeights(t *testing.T) {
|
||||
"Float32": float32(11),
|
||||
"Float64": float64(12),
|
||||
}
|
||||
c := new(checker.Checker)
|
||||
for a := range types {
|
||||
for b := range types {
|
||||
tree, err := parser.Parse(fmt.Sprintf("%s + %s", a, b))
|
||||
@@ -846,7 +849,7 @@ func TestCheck_TypeWeights(t *testing.T) {
|
||||
|
||||
config := conf.New(types)
|
||||
|
||||
_, err = checker.Check(tree, config)
|
||||
_, err = c.Check(tree, config)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
@@ -926,6 +929,7 @@ func TestCheck_Function_types_are_checked(t *testing.T) {
|
||||
|
||||
config := conf.CreateNew()
|
||||
add(config)
|
||||
c := new(checker.Checker)
|
||||
|
||||
tests := []string{
|
||||
"add(1)",
|
||||
@@ -938,7 +942,7 @@ func TestCheck_Function_types_are_checked(t *testing.T) {
|
||||
tree, err := parser.Parse(test)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = checker.Check(tree, config)
|
||||
_, err = c.Check(tree, config)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, reflect.Int, tree.Node.Type().Kind())
|
||||
})
|
||||
@@ -948,7 +952,7 @@ func TestCheck_Function_types_are_checked(t *testing.T) {
|
||||
tree, err := parser.Parse("add(1, '2')")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = checker.Check(tree, config)
|
||||
_, err = c.Check(tree, config)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "cannot use string as argument (type int) to call add (1:8)\n | add(1, '2')\n | .......^", err.Error())
|
||||
})
|
||||
@@ -1046,12 +1050,13 @@ func TestCheck_env_keyword(t *testing.T) {
|
||||
{`$env[name]`, reflect.Interface},
|
||||
}
|
||||
|
||||
c := new(checker.Checker)
|
||||
for _, test := range tests {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
tree, err := parser.Parse(test.input)
|
||||
require.NoError(t, err)
|
||||
|
||||
rtype, err := checker.Check(tree, conf.New(env))
|
||||
rtype, err := c.Check(tree, conf.New(env))
|
||||
require.NoError(t, err)
|
||||
require.True(t, rtype.Kind() == test.want, fmt.Sprintf("expected %s, got %s", test.want, rtype.Kind()))
|
||||
})
|
||||
@@ -1067,12 +1072,13 @@ func TestCheck_builtin_without_call(t *testing.T) {
|
||||
{`string.A`, "type func(interface {}) string has no field A (1:8)\n | string.A\n | .......^"},
|
||||
}
|
||||
|
||||
c := new(checker.Checker)
|
||||
for _, test := range tests {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
tree, err := parser.Parse(test.input)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = checker.Check(tree, conf.New(nil))
|
||||
_, err = c.Check(tree, conf.New(nil))
|
||||
require.Error(t, err)
|
||||
require.Equal(t, test.err, err.Error())
|
||||
})
|
||||
@@ -1134,13 +1140,14 @@ func TestCheck_types(t *testing.T) {
|
||||
{`arr | filter(.value contains "a") | filter(.value == 0)`, `invalid operation: == (mismatched types string and int)`},
|
||||
}
|
||||
|
||||
c := new(checker.Checker)
|
||||
for _, test := range tests {
|
||||
t.Run(test.code, func(t *testing.T) {
|
||||
tree, err := parser.Parse(test.code)
|
||||
require.NoError(t, err)
|
||||
|
||||
config := conf.New(env)
|
||||
_, err = checker.Check(tree, config)
|
||||
_, err = c.Check(tree, config)
|
||||
if test.err == noerr {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
|
||||
+11
-14
@@ -8,22 +8,19 @@ import (
|
||||
"github.com/expr-lang/expr/vm"
|
||||
)
|
||||
|
||||
func FieldIndex(env Nature, node ast.Node) (bool, []int, string) {
|
||||
func FieldIndex(c *Cache, env Nature, node ast.Node) (bool, []int, string) {
|
||||
switch n := node.(type) {
|
||||
case *ast.IdentifierNode:
|
||||
if env.Kind() == reflect.Struct {
|
||||
if field, ok := env.Get(n.Value); ok && len(field.FieldIndex) > 0 {
|
||||
return true, field.FieldIndex, n.Value
|
||||
}
|
||||
if idx, ok := env.FieldIndex(c, n.Value); ok {
|
||||
return true, idx, n.Value
|
||||
}
|
||||
case *ast.MemberNode:
|
||||
base := n.Node.Nature()
|
||||
base = base.Deref()
|
||||
if base.Kind() == reflect.Struct {
|
||||
base = base.Deref(c)
|
||||
if base.Kind == reflect.Struct {
|
||||
if prop, ok := n.Property.(*ast.StringNode); ok {
|
||||
name := prop.Value
|
||||
if field, ok := base.FieldByName(name); ok {
|
||||
return true, field.FieldIndex, name
|
||||
if idx, ok := base.FieldIndex(c, prop.Value); ok {
|
||||
return true, idx, prop.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,11 +28,11 @@ func FieldIndex(env Nature, node ast.Node) (bool, []int, string) {
|
||||
return false, nil, ""
|
||||
}
|
||||
|
||||
func MethodIndex(env Nature, node ast.Node) (bool, int, string) {
|
||||
func MethodIndex(c *Cache, env Nature, node ast.Node) (bool, int, string) {
|
||||
switch n := node.(type) {
|
||||
case *ast.IdentifierNode:
|
||||
if env.Kind() == reflect.Struct {
|
||||
if m, ok := env.Get(n.Value); ok {
|
||||
if env.Kind == reflect.Struct {
|
||||
if m, ok := env.Get(c, n.Value); ok && m.TypeData != nil {
|
||||
return m.Method, m.MethodIndex, n.Value
|
||||
}
|
||||
}
|
||||
@@ -121,7 +118,7 @@ func IsFastFunc(fn reflect.Type, method bool) bool {
|
||||
fn.NumOut() == 1 &&
|
||||
fn.Out(0).Kind() == reflect.Interface {
|
||||
rest := fn.In(fn.NumIn() - 1) // function has only one param for functions and two for methods
|
||||
if kind(rest) == reflect.Slice && rest.Elem().Kind() == reflect.Interface {
|
||||
if rest != nil && rest.Kind() == reflect.Slice && rest.Elem().Kind() == reflect.Interface {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
+464
-158
@@ -1,247 +1,450 @@
|
||||
package nature
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/expr-lang/expr/builtin"
|
||||
"github.com/expr-lang/expr/internal/deref"
|
||||
)
|
||||
|
||||
var (
|
||||
unknown = Nature{}
|
||||
intType = reflect.TypeOf(0)
|
||||
floatType = reflect.TypeOf(float64(0))
|
||||
arrayType = reflect.TypeOf([]any{})
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
durationType = reflect.TypeOf(time.Duration(0))
|
||||
|
||||
builtinInt = map[reflect.Type]struct{}{
|
||||
reflect.TypeOf(int(0)): {},
|
||||
reflect.TypeOf(int8(0)): {},
|
||||
reflect.TypeOf(int16(0)): {},
|
||||
reflect.TypeOf(int32(0)): {},
|
||||
reflect.TypeOf(int64(0)): {},
|
||||
reflect.TypeOf(uintptr(0)): {},
|
||||
reflect.TypeOf(uint(0)): {},
|
||||
reflect.TypeOf(uint8(0)): {},
|
||||
reflect.TypeOf(uint16(0)): {},
|
||||
reflect.TypeOf(uint32(0)): {},
|
||||
reflect.TypeOf(uint64(0)): {},
|
||||
}
|
||||
builtinFloat = map[reflect.Type]struct{}{
|
||||
reflect.TypeOf(float32(0)): {},
|
||||
reflect.TypeOf(float64(0)): {},
|
||||
}
|
||||
)
|
||||
|
||||
type NatureCheck int
|
||||
|
||||
const (
|
||||
_ NatureCheck = iota
|
||||
BoolCheck
|
||||
StringCheck
|
||||
IntegerCheck
|
||||
NumberCheck
|
||||
MapCheck
|
||||
ArrayCheck
|
||||
TimeCheck
|
||||
DurationCheck
|
||||
)
|
||||
|
||||
type Nature struct {
|
||||
Type reflect.Type // Type of the value. If nil, then value is unknown.
|
||||
Func *builtin.Function // Used to pass function type from callee to CallNode.
|
||||
ArrayOf *Nature // Elem nature of array type (usually Type is []any, but ArrayOf can be any nature).
|
||||
PredicateOut *Nature // Out nature of predicate.
|
||||
// The order of the fields matter, check alignment before making changes.
|
||||
|
||||
Type reflect.Type // Type of the value. If nil, then value is unknown.
|
||||
Kind reflect.Kind // Kind of the value.
|
||||
|
||||
*TypeData
|
||||
|
||||
// Ref is a reference used for multiple, disjoint purposes. When the Nature
|
||||
// is for a:
|
||||
// - Predicate: then Ref is the nature of the Out of the predicate.
|
||||
// - Array-like types: then Ref is the Elem nature of array type (usually Type is []any, but ArrayOf can be any nature).
|
||||
Ref *Nature
|
||||
|
||||
Nil bool // If value is nil.
|
||||
Strict bool // If map is types.StrictMap.
|
||||
Method bool // If value retrieved from method. Usually used to determine amount of in arguments.
|
||||
IsInteger bool // If it's a builtin integer or unsigned integer type.
|
||||
IsFloat bool // If it's a builtin float type.
|
||||
}
|
||||
|
||||
type TypeData struct {
|
||||
methodset *methodset // optional to avoid the map in *Cache
|
||||
|
||||
*structData
|
||||
|
||||
// map-only data
|
||||
Fields map[string]Nature // Fields of map type.
|
||||
DefaultMapValue *Nature // Default value of map type.
|
||||
Strict bool // If map is types.StrictMap.
|
||||
Nil bool // If value is nil.
|
||||
Method bool // If value retrieved from method. Usually used to determine amount of in arguments.
|
||||
|
||||
// callable-only data
|
||||
Func *builtin.Function // Used to pass function type from callee to CallNode.
|
||||
MethodIndex int // Index of method in type.
|
||||
FieldIndex []int // Index of field in type.
|
||||
inElem, outZero *Nature
|
||||
numIn, numOut int
|
||||
|
||||
isVariadic bool
|
||||
isVariadicSet bool
|
||||
numInSet bool
|
||||
numOutSet bool
|
||||
}
|
||||
|
||||
func (n Nature) IsAny() bool {
|
||||
return n.Kind() == reflect.Interface && n.NumMethods() == 0
|
||||
// Cache is a shared cache of type information. It is only used in the stages
|
||||
// where type information becomes relevant, so packages like ast, parser, types,
|
||||
// and lexer do not need to use the cache because they don't need any service
|
||||
// from the Nature type, they only describe. However, when receiving a Nature
|
||||
// from one of those packages, the cache must be set immediately.
|
||||
type Cache struct {
|
||||
methods map[reflect.Type]*methodset
|
||||
structs map[reflect.Type]Nature
|
||||
}
|
||||
|
||||
func (n Nature) IsUnknown() bool {
|
||||
switch {
|
||||
case n.Type == nil && !n.Nil:
|
||||
return true
|
||||
case n.IsAny():
|
||||
return true
|
||||
// NatureOf returns a Nature describing "i". If "i" is nil then it returns a
|
||||
// Nature describing the value "nil".
|
||||
func (c *Cache) NatureOf(i any) Nature {
|
||||
// reflect.TypeOf(nil) returns nil, but in FromType we want to differentiate
|
||||
// what nil means for us
|
||||
if i == nil {
|
||||
return Nature{Nil: true}
|
||||
}
|
||||
return false
|
||||
return c.FromType(reflect.TypeOf(i))
|
||||
}
|
||||
|
||||
func (n Nature) String() string {
|
||||
// FromType returns a Nature describing a value of type "t". If "t" is nil then
|
||||
// it returns a Nature describing an unknown value.
|
||||
func (c *Cache) FromType(t reflect.Type) Nature {
|
||||
if t == nil {
|
||||
return Nature{}
|
||||
}
|
||||
var td *TypeData
|
||||
var isInteger, isFloat bool
|
||||
k := t.Kind()
|
||||
switch k {
|
||||
case reflect.Struct:
|
||||
// c can be nil when we call the package function FromType, which uses a
|
||||
// nil *Cache to call this method.
|
||||
if c != nil {
|
||||
return c.getStruct(t)
|
||||
}
|
||||
fallthrough
|
||||
case reflect.Func:
|
||||
td = new(TypeData)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
_, isInteger = builtinInt[t]
|
||||
case reflect.Float32, reflect.Float64:
|
||||
_, isFloat = builtinFloat[t]
|
||||
}
|
||||
return Nature{
|
||||
Type: t,
|
||||
Kind: k,
|
||||
TypeData: td,
|
||||
IsInteger: isInteger,
|
||||
IsFloat: isFloat,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) getStruct(t reflect.Type) Nature {
|
||||
if c.structs == nil {
|
||||
c.structs = map[reflect.Type]Nature{}
|
||||
} else if nt, ok := c.structs[t]; ok {
|
||||
return nt
|
||||
}
|
||||
nt := Nature{
|
||||
Type: t,
|
||||
Kind: reflect.Struct,
|
||||
TypeData: &TypeData{
|
||||
structData: &structData{
|
||||
rType: t,
|
||||
numField: t.NumField(),
|
||||
anonIdx: -1, // do not lookup embedded fields yet
|
||||
},
|
||||
},
|
||||
}
|
||||
c.structs[t] = nt
|
||||
return nt
|
||||
}
|
||||
|
||||
func (c *Cache) getMethodset(t reflect.Type, k reflect.Kind) *methodset {
|
||||
if t == nil || c == nil {
|
||||
return nil
|
||||
}
|
||||
if c.methods == nil {
|
||||
c.methods = map[reflect.Type]*methodset{
|
||||
t: nil,
|
||||
}
|
||||
} else if s, ok := c.methods[t]; ok {
|
||||
return s
|
||||
}
|
||||
numMethod := t.NumMethod()
|
||||
if numMethod < 1 {
|
||||
c.methods[t] = nil // negative cache
|
||||
return nil
|
||||
}
|
||||
s := &methodset{
|
||||
rType: t,
|
||||
kind: k,
|
||||
numMethod: numMethod,
|
||||
}
|
||||
c.methods[t] = s
|
||||
return s
|
||||
}
|
||||
|
||||
// NatureOf calls NatureOf on a nil *Cache. See the comment on Cache.
|
||||
func NatureOf(i any) Nature {
|
||||
var c *Cache
|
||||
return c.NatureOf(i)
|
||||
}
|
||||
|
||||
// FromType calls FromType on a nil *Cache. See the comment on Cache.
|
||||
func FromType(t reflect.Type) Nature {
|
||||
var c *Cache
|
||||
return c.FromType(t)
|
||||
}
|
||||
|
||||
func ArrayFromType(c *Cache, t reflect.Type) Nature {
|
||||
elem := c.FromType(t)
|
||||
nt := c.FromType(arrayType)
|
||||
nt.Ref = &elem
|
||||
return nt
|
||||
}
|
||||
|
||||
func (n *Nature) IsAny(c *Cache) bool {
|
||||
return n.Type != nil && n.Kind == reflect.Interface && n.NumMethods(c) == 0
|
||||
}
|
||||
|
||||
func (n *Nature) IsUnknown(c *Cache) bool {
|
||||
return n.Type == nil && !n.Nil || n.IsAny(c)
|
||||
}
|
||||
|
||||
func (n *Nature) String() string {
|
||||
if n.Type != nil {
|
||||
return n.Type.String()
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func (n Nature) Deref() Nature {
|
||||
if n.Type != nil {
|
||||
n.Type = deref.Type(n.Type)
|
||||
func (n *Nature) Deref(c *Cache) Nature {
|
||||
t, _, changed := deref.TypeKind(n.Type, n.Kind)
|
||||
if !changed {
|
||||
return *n
|
||||
}
|
||||
return n
|
||||
return c.FromType(t)
|
||||
}
|
||||
|
||||
func (n Nature) Kind() reflect.Kind {
|
||||
if n.Type != nil {
|
||||
return n.Type.Kind()
|
||||
func (n *Nature) Key(c *Cache) Nature {
|
||||
if n.Kind == reflect.Map {
|
||||
return c.FromType(n.Type.Key())
|
||||
}
|
||||
return reflect.Invalid
|
||||
return Nature{}
|
||||
}
|
||||
|
||||
func (n Nature) Key() Nature {
|
||||
if n.Kind() == reflect.Map {
|
||||
return Nature{Type: n.Type.Key()}
|
||||
}
|
||||
return unknown
|
||||
}
|
||||
|
||||
func (n Nature) Elem() Nature {
|
||||
switch n.Kind() {
|
||||
func (n *Nature) Elem(c *Cache) Nature {
|
||||
switch n.Kind {
|
||||
case reflect.Ptr:
|
||||
return Nature{Type: n.Type.Elem()}
|
||||
return c.FromType(n.Type.Elem())
|
||||
case reflect.Map:
|
||||
if n.DefaultMapValue != nil {
|
||||
if n.TypeData != nil && n.DefaultMapValue != nil {
|
||||
return *n.DefaultMapValue
|
||||
}
|
||||
return Nature{Type: n.Type.Elem()}
|
||||
case reflect.Array, reflect.Slice:
|
||||
if n.ArrayOf != nil {
|
||||
return *n.ArrayOf
|
||||
return c.FromType(n.Type.Elem())
|
||||
case reflect.Slice, reflect.Array:
|
||||
if n.Ref != nil {
|
||||
return *n.Ref
|
||||
}
|
||||
return Nature{Type: n.Type.Elem()}
|
||||
return c.FromType(n.Type.Elem())
|
||||
}
|
||||
return unknown
|
||||
return Nature{}
|
||||
}
|
||||
|
||||
func (n Nature) AssignableTo(nt Nature) bool {
|
||||
func (n *Nature) AssignableTo(nt Nature) bool {
|
||||
if n.Nil {
|
||||
// Untyped nil is assignable to any interface, but implements only the empty interface.
|
||||
if nt.IsAny() {
|
||||
switch nt.Kind {
|
||||
case reflect.Pointer, reflect.Interface, reflect.Chan, reflect.Func,
|
||||
reflect.Map, reflect.Slice:
|
||||
// nil can be assigned to these kinds
|
||||
return true
|
||||
}
|
||||
}
|
||||
if n.Type == nil || nt.Type == nil {
|
||||
if n.Type == nil || nt.Type == nil ||
|
||||
n.Kind != nt.Kind && nt.Kind != reflect.Interface {
|
||||
return false
|
||||
}
|
||||
return n.Type.AssignableTo(nt.Type)
|
||||
}
|
||||
|
||||
func (n Nature) NumMethods() int {
|
||||
if n.Type == nil {
|
||||
return 0
|
||||
func (n *Nature) getMethodset(c *Cache) *methodset {
|
||||
if n.TypeData != nil && n.TypeData.methodset != nil {
|
||||
return n.TypeData.methodset
|
||||
}
|
||||
return n.Type.NumMethod()
|
||||
s := c.getMethodset(n.Type, n.Kind)
|
||||
if n.TypeData != nil {
|
||||
n.TypeData.methodset = s // cache locally if possible
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (n Nature) MethodByName(name string) (Nature, bool) {
|
||||
if n.Type == nil {
|
||||
return unknown, false
|
||||
}
|
||||
method, ok := n.Type.MethodByName(name)
|
||||
if !ok {
|
||||
return unknown, false
|
||||
}
|
||||
|
||||
if n.Type.Kind() == reflect.Interface {
|
||||
// In case of interface type method will not have a receiver,
|
||||
// and to prevent checker decreasing numbers of in arguments
|
||||
// return method type as not method (second argument is false).
|
||||
|
||||
// Also, we can not use m.Index here, because it will be
|
||||
// different indexes for different types which implement
|
||||
// the same interface.
|
||||
return Nature{Type: method.Type}, true
|
||||
} else {
|
||||
return Nature{
|
||||
Type: method.Type,
|
||||
Method: true,
|
||||
MethodIndex: method.Index,
|
||||
}, true
|
||||
func (n *Nature) NumMethods(c *Cache) int {
|
||||
if s := n.getMethodset(c); s != nil {
|
||||
return s.numMethod
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (n Nature) NumIn() int {
|
||||
if n.Type == nil {
|
||||
return 0
|
||||
func (n *Nature) MethodByName(c *Cache, name string) (Nature, bool) {
|
||||
if s := n.getMethodset(c); s != nil {
|
||||
if m := s.method(c, name); m != nil {
|
||||
return m.nature, true
|
||||
}
|
||||
}
|
||||
return n.Type.NumIn()
|
||||
return Nature{}, false
|
||||
}
|
||||
|
||||
func (n Nature) In(i int) Nature {
|
||||
if n.Type == nil {
|
||||
return unknown
|
||||
func (n *Nature) NumIn() int {
|
||||
if n.numInSet {
|
||||
return n.numIn
|
||||
}
|
||||
return Nature{Type: n.Type.In(i)}
|
||||
n.numInSet = true
|
||||
n.numIn = n.Type.NumIn()
|
||||
return n.numIn
|
||||
}
|
||||
|
||||
func (n Nature) NumOut() int {
|
||||
if n.Type == nil {
|
||||
return 0
|
||||
func (n *Nature) InElem(c *Cache, i int) Nature {
|
||||
if n.inElem == nil {
|
||||
n2 := c.FromType(n.Type.In(i))
|
||||
n2 = n2.Elem(c)
|
||||
n.inElem = &n2
|
||||
}
|
||||
return n.Type.NumOut()
|
||||
return *n.inElem
|
||||
}
|
||||
|
||||
func (n Nature) Out(i int) Nature {
|
||||
if n.Type == nil {
|
||||
return unknown
|
||||
}
|
||||
return Nature{Type: n.Type.Out(i)}
|
||||
func (n *Nature) In(c *Cache, i int) Nature {
|
||||
return c.FromType(n.Type.In(i))
|
||||
}
|
||||
|
||||
func (n Nature) IsVariadic() bool {
|
||||
if n.Type == nil {
|
||||
return false
|
||||
}
|
||||
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) PkgPath() string {
|
||||
if n.Type == nil {
|
||||
return ""
|
||||
}
|
||||
return n.Type.PkgPath()
|
||||
}
|
||||
|
||||
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
|
||||
func (n *Nature) IsFirstArgUnknown(c *Cache) bool {
|
||||
if n.Type != nil {
|
||||
n2 := c.FromType(n.Type.In(0))
|
||||
return n2.IsUnknown(c)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (n Nature) Get(name string) (Nature, bool) {
|
||||
if n.Type == nil {
|
||||
return unknown, false
|
||||
func (n *Nature) NumOut() int {
|
||||
if n.numOutSet {
|
||||
return n.numOut
|
||||
}
|
||||
|
||||
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.Fields[name]; ok {
|
||||
return f, true
|
||||
}
|
||||
}
|
||||
return unknown, false
|
||||
n.numOutSet = true
|
||||
n.numOut = n.Type.NumOut()
|
||||
return n.numOut
|
||||
}
|
||||
|
||||
func (n Nature) All() map[string]Nature {
|
||||
func (n *Nature) Out(c *Cache, i int) Nature {
|
||||
if i != 0 {
|
||||
return n.out(c, i)
|
||||
}
|
||||
if n.outZero != nil {
|
||||
return *n.outZero
|
||||
}
|
||||
nt := n.out(c, 0)
|
||||
n.outZero = &nt
|
||||
return nt
|
||||
}
|
||||
|
||||
func (n *Nature) out(c *Cache, i int) Nature {
|
||||
if n.Type == nil {
|
||||
return Nature{}
|
||||
}
|
||||
return c.FromType(n.Type.Out(i))
|
||||
}
|
||||
|
||||
func (n *Nature) IsVariadic() bool {
|
||||
if n.isVariadicSet {
|
||||
return n.isVariadic
|
||||
}
|
||||
n.isVariadicSet = true
|
||||
n.isVariadic = n.Type.IsVariadic()
|
||||
return n.isVariadic
|
||||
}
|
||||
|
||||
func (n *Nature) FieldByName(c *Cache, name string) (Nature, bool) {
|
||||
if n.Kind != reflect.Struct {
|
||||
return Nature{}, false
|
||||
}
|
||||
var sd *structData
|
||||
if n.TypeData != nil && n.structData != nil {
|
||||
sd = n.structData
|
||||
} else {
|
||||
sd = c.getStruct(n.Type).structData
|
||||
}
|
||||
if sf := sd.structField(c, nil, name); sf != nil {
|
||||
return sf.Nature, true
|
||||
}
|
||||
return Nature{}, false
|
||||
}
|
||||
|
||||
func (n *Nature) IsFastMap() bool {
|
||||
return n.Type != nil &&
|
||||
n.Type.Kind() == reflect.Map &&
|
||||
n.Type.Key().Kind() == reflect.String &&
|
||||
n.Type.Elem().Kind() == reflect.Interface
|
||||
}
|
||||
|
||||
func (n *Nature) Get(c *Cache, name string) (Nature, bool) {
|
||||
if n.Kind == reflect.Map && n.TypeData != nil {
|
||||
f, ok := n.Fields[name]
|
||||
return f, ok
|
||||
}
|
||||
return n.getSlow(c, name)
|
||||
}
|
||||
|
||||
func (n *Nature) getSlow(c *Cache, name string) (Nature, bool) {
|
||||
if nt, ok := n.MethodByName(c, name); ok {
|
||||
return nt, true
|
||||
}
|
||||
if n.Kind == reflect.Struct {
|
||||
if sf := n.structField(c, nil, name); sf != nil {
|
||||
return sf.Nature, true
|
||||
}
|
||||
}
|
||||
return Nature{}, false
|
||||
}
|
||||
|
||||
func (n *Nature) FieldIndex(c *Cache, name string) ([]int, bool) {
|
||||
if n.Kind != reflect.Struct {
|
||||
return nil, false
|
||||
}
|
||||
if sf := n.structField(c, nil, name); sf != nil {
|
||||
return sf.Index, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (n *Nature) All(c *Cache) map[string]Nature {
|
||||
table := make(map[string]Nature)
|
||||
|
||||
if n.Type == nil {
|
||||
return table
|
||||
}
|
||||
|
||||
for i := 0; i < n.Type.NumMethod(); i++ {
|
||||
for i := 0; i < n.NumMethods(c); i++ {
|
||||
method := n.Type.Method(i)
|
||||
table[method.Name] = Nature{
|
||||
Type: method.Type,
|
||||
Method: true,
|
||||
MethodIndex: method.Index,
|
||||
nt := c.FromType(method.Type)
|
||||
if nt.TypeData == nil {
|
||||
nt.TypeData = new(TypeData)
|
||||
}
|
||||
nt.Method = true
|
||||
nt.MethodIndex = method.Index
|
||||
table[method.Name] = nt
|
||||
}
|
||||
|
||||
t := deref.Type(n.Type)
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Struct:
|
||||
for name, nt := range StructFields(t) {
|
||||
for name, nt := range StructFields(c, t) {
|
||||
if _, ok := table[name]; ok {
|
||||
continue
|
||||
}
|
||||
@@ -249,13 +452,116 @@ func (n Nature) All() map[string]Nature {
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
for key, nt := range n.Fields {
|
||||
if _, ok := table[key]; ok {
|
||||
continue
|
||||
if n.TypeData != nil {
|
||||
for key, nt := range n.Fields {
|
||||
if _, ok := table[key]; ok {
|
||||
continue
|
||||
}
|
||||
table[key] = nt
|
||||
}
|
||||
table[key] = nt
|
||||
}
|
||||
}
|
||||
|
||||
return table
|
||||
}
|
||||
|
||||
func (n *Nature) IsNumber() bool {
|
||||
return n.IsInteger || n.IsFloat
|
||||
}
|
||||
|
||||
func (n *Nature) PromoteNumericNature(c *Cache, rhs Nature) Nature {
|
||||
if n.IsUnknown(c) || rhs.IsUnknown(c) {
|
||||
return Nature{}
|
||||
}
|
||||
if n.IsFloat || rhs.IsFloat {
|
||||
return c.FromType(floatType)
|
||||
}
|
||||
return c.FromType(intType)
|
||||
}
|
||||
|
||||
func (n *Nature) IsTime() bool {
|
||||
return n.Type == timeType
|
||||
}
|
||||
|
||||
func (n *Nature) IsDuration() bool {
|
||||
return n.Type == durationType
|
||||
}
|
||||
|
||||
func (n *Nature) IsBool() bool {
|
||||
return n.Kind == reflect.Bool
|
||||
}
|
||||
|
||||
func (n *Nature) IsString() bool {
|
||||
return n.Kind == reflect.String
|
||||
}
|
||||
|
||||
func (n *Nature) IsArray() bool {
|
||||
return n.Kind == reflect.Slice || n.Kind == reflect.Array
|
||||
}
|
||||
|
||||
func (n *Nature) IsMap() bool {
|
||||
return n.Kind == reflect.Map
|
||||
}
|
||||
|
||||
func (n *Nature) IsStruct() bool {
|
||||
return n.Kind == reflect.Struct
|
||||
}
|
||||
|
||||
func (n *Nature) IsFunc() bool {
|
||||
return n.Kind == reflect.Func
|
||||
}
|
||||
|
||||
func (n *Nature) IsPointer() bool {
|
||||
return n.Kind == reflect.Ptr
|
||||
}
|
||||
|
||||
func (n *Nature) IsAnyOf(cs ...NatureCheck) bool {
|
||||
var result bool
|
||||
for i := 0; i < len(cs) && !result; i++ {
|
||||
switch cs[i] {
|
||||
case BoolCheck:
|
||||
result = n.IsBool()
|
||||
case StringCheck:
|
||||
result = n.IsString()
|
||||
case IntegerCheck:
|
||||
result = n.IsInteger
|
||||
case NumberCheck:
|
||||
result = n.IsNumber()
|
||||
case MapCheck:
|
||||
result = n.IsMap()
|
||||
case ArrayCheck:
|
||||
result = n.IsArray()
|
||||
case TimeCheck:
|
||||
result = n.IsTime()
|
||||
case DurationCheck:
|
||||
result = n.IsDuration()
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown check value %d", cs[i]))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *Nature) ComparableTo(c *Cache, rhs Nature) bool {
|
||||
return n.IsUnknown(c) || rhs.IsUnknown(c) ||
|
||||
n.Nil || rhs.Nil ||
|
||||
n.IsNumber() && rhs.IsNumber() ||
|
||||
n.IsDuration() && rhs.IsDuration() ||
|
||||
n.IsTime() && rhs.IsTime() ||
|
||||
n.IsArray() && rhs.IsArray() ||
|
||||
n.AssignableTo(rhs)
|
||||
}
|
||||
|
||||
func (n *Nature) MaybeCompatible(c *Cache, rhs Nature, cs ...NatureCheck) bool {
|
||||
nIsUnknown := n.IsUnknown(c)
|
||||
rshIsUnknown := rhs.IsUnknown(c)
|
||||
return nIsUnknown && rshIsUnknown ||
|
||||
nIsUnknown && rhs.IsAnyOf(cs...) ||
|
||||
rshIsUnknown && n.IsAnyOf(cs...)
|
||||
}
|
||||
|
||||
func (n *Nature) MakeArrayOf(c *Cache) Nature {
|
||||
nt := c.FromType(arrayType)
|
||||
nt.Ref = n
|
||||
return nt
|
||||
}
|
||||
|
||||
+205
-62
@@ -6,84 +6,227 @@ import (
|
||||
"github.com/expr-lang/expr/internal/deref"
|
||||
)
|
||||
|
||||
func fieldName(field reflect.StructField) (string, bool) {
|
||||
switch taggedName := field.Tag.Get("expr"); taggedName {
|
||||
func fieldName(fieldName string, tag reflect.StructTag) (string, bool) {
|
||||
switch taggedName := tag.Get("expr"); taggedName {
|
||||
case "-":
|
||||
return "", false
|
||||
case "":
|
||||
return field.Name, true
|
||||
return fieldName, true
|
||||
default:
|
||||
return taggedName, true
|
||||
}
|
||||
}
|
||||
|
||||
func fetchField(t reflect.Type, name string) (reflect.StructField, bool) {
|
||||
// If t is not a struct, early return.
|
||||
if t.Kind() != reflect.Struct {
|
||||
return reflect.StructField{}, false
|
||||
}
|
||||
type structData struct {
|
||||
rType reflect.Type
|
||||
fields map[string]*structField
|
||||
numField, ownIdx, anonIdx int
|
||||
|
||||
// First check all structs fields.
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
// Search all fields, even embedded structs.
|
||||
if n, ok := fieldName(field); ok && n == name {
|
||||
return field, true
|
||||
}
|
||||
}
|
||||
|
||||
// Second check fields of embedded structs.
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
anon := t.Field(i)
|
||||
if anon.Anonymous {
|
||||
anonType := anon.Type
|
||||
if anonType.Kind() == reflect.Pointer {
|
||||
anonType = anonType.Elem()
|
||||
}
|
||||
if field, ok := fetchField(anonType, name); ok {
|
||||
field.Index = append(anon.Index, field.Index...)
|
||||
return field, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return reflect.StructField{}, false
|
||||
curParent, curChild *structData
|
||||
curChildIndex []int
|
||||
}
|
||||
|
||||
func StructFields(t reflect.Type) map[string]Nature {
|
||||
table := make(map[string]Nature)
|
||||
type structField struct {
|
||||
Nature
|
||||
Index []int
|
||||
}
|
||||
|
||||
t = deref.Type(t)
|
||||
func (s *structData) finished() bool {
|
||||
return s.ownIdx >= s.numField && // no own fields left to visit
|
||||
s.anonIdx >= s.numField && // no embedded fields to visit
|
||||
s.curChild == nil // no child in process of visiting
|
||||
}
|
||||
|
||||
func (s *structData) structField(c *Cache, parentEmbed *structData, name string) *structField {
|
||||
if s.fields == nil {
|
||||
if s.numField > 0 {
|
||||
s.fields = make(map[string]*structField, s.numField)
|
||||
}
|
||||
} else if f := s.fields[name]; f != nil {
|
||||
return f
|
||||
}
|
||||
if s.finished() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lookup own fields first.
|
||||
for ; s.ownIdx < s.numField; s.ownIdx++ {
|
||||
field := s.rType.Field(s.ownIdx)
|
||||
// BUG: we should skip if !field.IsExported() here
|
||||
|
||||
if field.Anonymous && s.anonIdx < 0 {
|
||||
// start iterating anon fields on the first instead of zero
|
||||
s.anonIdx = s.ownIdx
|
||||
}
|
||||
fName, ok := fieldName(field.Name, field.Tag)
|
||||
if !ok || fName == "" {
|
||||
// name can still be empty for a type created at runtime with
|
||||
// reflect
|
||||
continue
|
||||
}
|
||||
nt := c.FromType(field.Type)
|
||||
sf := &structField{
|
||||
Nature: nt,
|
||||
Index: field.Index,
|
||||
}
|
||||
s.fields[fName] = sf
|
||||
if parentEmbed != nil {
|
||||
parentEmbed.trySet(fName, sf)
|
||||
}
|
||||
if fName == name {
|
||||
return sf
|
||||
}
|
||||
}
|
||||
|
||||
if s.curChild != nil {
|
||||
sf := s.findInEmbedded(c, parentEmbed, s.curChild, s.curChildIndex, name)
|
||||
if sf != nil {
|
||||
return sf
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup embedded fields through anon own fields
|
||||
for ; s.anonIdx >= 0 && s.anonIdx < s.numField; s.anonIdx++ {
|
||||
field := s.rType.Field(s.anonIdx)
|
||||
// we do enter embedded non-exported types because they could contain
|
||||
// exported fields
|
||||
if !field.Anonymous {
|
||||
continue
|
||||
}
|
||||
t, k, _ := deref.TypeKind(field.Type, field.Type.Kind())
|
||||
if k != reflect.Struct {
|
||||
continue
|
||||
}
|
||||
|
||||
childEmbed := c.getStruct(t).structData
|
||||
sf := s.findInEmbedded(c, parentEmbed, childEmbed, field.Index, name)
|
||||
if sf != nil {
|
||||
return sf
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *structData) findInEmbedded(
|
||||
c *Cache,
|
||||
parentEmbed, childEmbed *structData,
|
||||
childIndex []int,
|
||||
name string,
|
||||
) *structField {
|
||||
// Set current parent/child data. This allows trySet to handle child fields
|
||||
// and add them to our struct and to the parent as well if needed
|
||||
s.curParent = parentEmbed
|
||||
s.curChild = childEmbed
|
||||
s.curChildIndex = childIndex
|
||||
defer func() {
|
||||
// Ensure to cleanup references
|
||||
s.curParent = nil
|
||||
if childEmbed.finished() {
|
||||
// If the child can still have more fields to explore then keep it
|
||||
// referened to look it up again if we need to
|
||||
s.curChild = nil
|
||||
s.curChildIndex = nil
|
||||
}
|
||||
}()
|
||||
|
||||
// See if the child has already cached its fields. This is still important
|
||||
// to check even if it's the s.unfinishedEmbedded because it may have
|
||||
// explored new fields since the last time we visited it
|
||||
for name, sf := range childEmbed.fields {
|
||||
s.trySet(name, sf)
|
||||
}
|
||||
|
||||
// Recheck if we have what we needed from the above sync
|
||||
if sf := s.fields[name]; sf != nil {
|
||||
return sf
|
||||
}
|
||||
|
||||
// Try finding in the child again in case it hasn't finished
|
||||
if !childEmbed.finished() {
|
||||
if childEmbed.structField(c, s, name) != nil {
|
||||
return s.fields[name]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *structData) trySet(name string, sf *structField) {
|
||||
if _, ok := s.fields[name]; ok {
|
||||
return
|
||||
}
|
||||
sf = &structField{
|
||||
Nature: sf.Nature,
|
||||
Index: append(s.curChildIndex, sf.Index...),
|
||||
}
|
||||
s.fields[name] = sf
|
||||
if s.curParent != nil {
|
||||
s.curParent.trySet(name, sf)
|
||||
}
|
||||
}
|
||||
|
||||
func StructFields(c *Cache, t reflect.Type) map[string]Nature {
|
||||
table := make(map[string]Nature)
|
||||
if t == nil {
|
||||
return table
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
|
||||
if f.Anonymous {
|
||||
for name, typ := range StructFields(f.Type) {
|
||||
if _, ok := table[name]; ok {
|
||||
continue
|
||||
}
|
||||
typ.FieldIndex = append(f.Index, typ.FieldIndex...)
|
||||
table[name] = typ
|
||||
}
|
||||
}
|
||||
|
||||
name, ok := fieldName(f)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
table[name] = Nature{
|
||||
Type: f.Type,
|
||||
FieldIndex: f.Index,
|
||||
}
|
||||
|
||||
t, k, _ := deref.TypeKind(t, t.Kind())
|
||||
if k == reflect.Struct {
|
||||
// lookup for a field with an empty name, which will cause to never find a
|
||||
// match, meaning everything will have been cached.
|
||||
sd := c.getStruct(t).structData
|
||||
sd.structField(c, nil, "")
|
||||
for name, sf := range sd.fields {
|
||||
table[name] = sf.Nature
|
||||
}
|
||||
}
|
||||
|
||||
return table
|
||||
}
|
||||
|
||||
type methodset struct {
|
||||
rType reflect.Type
|
||||
kind reflect.Kind
|
||||
methods map[string]*method
|
||||
numMethod, idx int
|
||||
}
|
||||
|
||||
type method struct {
|
||||
reflect.Method
|
||||
nature Nature
|
||||
}
|
||||
|
||||
func (s *methodset) method(c *Cache, name string) *method {
|
||||
if s.methods == nil {
|
||||
s.methods = make(map[string]*method, s.numMethod)
|
||||
} else if m := s.methods[name]; m != nil {
|
||||
return m
|
||||
}
|
||||
for ; s.idx < s.numMethod; s.idx++ {
|
||||
rm := s.rType.Method(s.idx)
|
||||
if !rm.IsExported() {
|
||||
continue
|
||||
}
|
||||
nt := c.FromType(rm.Type)
|
||||
if s.rType.Kind() != reflect.Interface {
|
||||
nt.Method = true
|
||||
nt.MethodIndex = rm.Index
|
||||
// In case of interface type method will not have a receiver,
|
||||
// and to prevent checker decreasing numbers of in arguments
|
||||
// return method type as not method (second argument is false).
|
||||
|
||||
// Also, we can not use m.Index here, because it will be
|
||||
// different indexes for different types which implement
|
||||
// the same interface.
|
||||
}
|
||||
m := &method{
|
||||
Method: rm,
|
||||
nature: nt,
|
||||
}
|
||||
s.methods[rm.Name] = m
|
||||
if rm.Name == name {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
package checker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
. "github.com/expr-lang/expr/checker/nature"
|
||||
)
|
||||
|
||||
var (
|
||||
unknown = Nature{}
|
||||
nilNature = Nature{Nil: true}
|
||||
boolNature = Nature{Type: reflect.TypeOf(true)}
|
||||
integerNature = Nature{Type: reflect.TypeOf(0)}
|
||||
floatNature = Nature{Type: reflect.TypeOf(float64(0))}
|
||||
stringNature = Nature{Type: reflect.TypeOf("")}
|
||||
arrayNature = Nature{Type: reflect.TypeOf([]any{})}
|
||||
mapNature = Nature{Type: reflect.TypeOf(map[string]any{})}
|
||||
timeNature = Nature{Type: reflect.TypeOf(time.Time{})}
|
||||
durationNature = Nature{Type: reflect.TypeOf(time.Duration(0))}
|
||||
)
|
||||
|
||||
var (
|
||||
anyType = reflect.TypeOf(new(any)).Elem()
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
durationType = reflect.TypeOf(time.Duration(0))
|
||||
arrayType = reflect.TypeOf([]any{})
|
||||
)
|
||||
|
||||
func arrayOf(nt Nature) Nature {
|
||||
return Nature{
|
||||
Type: arrayType,
|
||||
ArrayOf: &nt,
|
||||
}
|
||||
}
|
||||
|
||||
func isNil(nt Nature) bool {
|
||||
return nt.Nil
|
||||
}
|
||||
|
||||
func combined(l, r Nature) Nature {
|
||||
if isUnknown(l) || isUnknown(r) {
|
||||
return unknown
|
||||
}
|
||||
if isFloat(l) || isFloat(r) {
|
||||
return floatNature
|
||||
}
|
||||
return integerNature
|
||||
}
|
||||
|
||||
func anyOf(nt Nature, fns ...func(Nature) bool) bool {
|
||||
for _, fn := range fns {
|
||||
if fn(nt) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func or(l, r Nature, fns ...func(Nature) bool) bool {
|
||||
if isUnknown(l) && isUnknown(r) {
|
||||
return true
|
||||
}
|
||||
if isUnknown(l) && anyOf(r, fns...) {
|
||||
return true
|
||||
}
|
||||
if isUnknown(r) && anyOf(l, fns...) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isUnknown(nt Nature) bool {
|
||||
return nt.IsUnknown()
|
||||
}
|
||||
|
||||
func isInteger(nt Nature) bool {
|
||||
switch nt.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
fallthrough
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return nt.PkgPath() == ""
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isFloat(nt Nature) bool {
|
||||
switch nt.Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return nt.PkgPath() == ""
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isNumber(nt Nature) bool {
|
||||
return isInteger(nt) || isFloat(nt)
|
||||
}
|
||||
|
||||
func isTime(nt Nature) bool {
|
||||
switch nt.Type {
|
||||
case timeType:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isDuration(nt Nature) bool {
|
||||
switch nt.Type {
|
||||
case durationType:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isBool(nt Nature) bool {
|
||||
switch nt.Kind() {
|
||||
case reflect.Bool:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isString(nt Nature) bool {
|
||||
switch nt.Kind() {
|
||||
case reflect.String:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isArray(nt Nature) bool {
|
||||
switch nt.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isMap(nt Nature) bool {
|
||||
switch nt.Kind() {
|
||||
case reflect.Map:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isStruct(nt Nature) bool {
|
||||
switch nt.Kind() {
|
||||
case reflect.Struct:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isFunc(nt Nature) bool {
|
||||
switch nt.Kind() {
|
||||
case reflect.Func:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func kind(t reflect.Type) reflect.Kind {
|
||||
if t == nil {
|
||||
return reflect.Invalid
|
||||
}
|
||||
return t.Kind()
|
||||
}
|
||||
|
||||
func isComparable(l, r Nature) bool {
|
||||
if isUnknown(l) || isUnknown(r) {
|
||||
return true
|
||||
}
|
||||
if isNil(l) || isNil(r) {
|
||||
return true
|
||||
}
|
||||
if isNumber(l) && isNumber(r) {
|
||||
return true
|
||||
}
|
||||
if isDuration(l) && isDuration(r) {
|
||||
return true
|
||||
}
|
||||
if isTime(l) && isTime(r) {
|
||||
return true
|
||||
}
|
||||
if isArray(l) && isArray(r) {
|
||||
return true
|
||||
}
|
||||
return l.AssignableTo(r)
|
||||
}
|
||||
+19
-10
@@ -5,6 +5,7 @@ import (
|
||||
"math"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/expr-lang/expr/ast"
|
||||
"github.com/expr-lang/expr/builtin"
|
||||
@@ -24,7 +25,7 @@ const (
|
||||
func Compile(tree *parser.Tree, config *conf.Config) (program *Program, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("%v", r)
|
||||
err = fmt.Errorf("%v\n%s", r, debug.Stack())
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -36,6 +37,12 @@ func Compile(tree *parser.Tree, config *conf.Config) (program *Program, err erro
|
||||
debugInfo: make(map[string]string),
|
||||
}
|
||||
|
||||
if config != nil {
|
||||
c.ntCache = &c.config.NtCache
|
||||
} else {
|
||||
c.ntCache = new(Cache)
|
||||
}
|
||||
|
||||
c.compile(tree.Node)
|
||||
|
||||
if c.config != nil {
|
||||
@@ -74,6 +81,7 @@ func Compile(tree *parser.Tree, config *conf.Config) (program *Program, err erro
|
||||
|
||||
type compiler struct {
|
||||
config *conf.Config
|
||||
ntCache *Cache
|
||||
locations []file.Location
|
||||
bytecode []Opcode
|
||||
variables int
|
||||
@@ -302,12 +310,12 @@ func (c *compiler) IdentifierNode(node *ast.IdentifierNode) {
|
||||
|
||||
if env.IsFastMap() {
|
||||
c.emit(OpLoadFast, c.addConstant(node.Value))
|
||||
} else if ok, index, name := checker.FieldIndex(env, node); ok {
|
||||
} else if ok, index, name := checker.FieldIndex(c.ntCache, env, node); ok {
|
||||
c.emit(OpLoadField, c.addConstant(&runtime.Field{
|
||||
Index: index,
|
||||
Path: []string{name},
|
||||
}))
|
||||
} else if ok, index, name := checker.MethodIndex(env, node); ok {
|
||||
} else if ok, index, name := checker.MethodIndex(c.ntCache, env, node); ok {
|
||||
c.emit(OpLoadMethod, c.addConstant(&runtime.Method{
|
||||
Name: name,
|
||||
Index: index,
|
||||
@@ -653,7 +661,7 @@ func (c *compiler) MemberNode(node *ast.MemberNode) {
|
||||
env = c.config.Env
|
||||
}
|
||||
|
||||
if ok, index, name := checker.MethodIndex(env, node); ok {
|
||||
if ok, index, name := checker.MethodIndex(c.ntCache, env, node); ok {
|
||||
c.compile(node.Node)
|
||||
c.emit(OpMethod, c.addConstant(&runtime.Method{
|
||||
Name: name,
|
||||
@@ -664,14 +672,14 @@ func (c *compiler) MemberNode(node *ast.MemberNode) {
|
||||
op := OpFetch
|
||||
base := node.Node
|
||||
|
||||
ok, index, nodeName := checker.FieldIndex(env, node)
|
||||
ok, index, nodeName := checker.FieldIndex(c.ntCache, env, node)
|
||||
path := []string{nodeName}
|
||||
|
||||
if ok {
|
||||
op = OpFetchField
|
||||
for !node.Optional {
|
||||
if ident, isIdent := base.(*ast.IdentifierNode); isIdent {
|
||||
if ok, identIndex, name := checker.FieldIndex(env, ident); ok {
|
||||
if ok, identIndex, name := checker.FieldIndex(c.ntCache, env, ident); ok {
|
||||
index = append(identIndex, index...)
|
||||
path = append([]string{name}, path...)
|
||||
c.emitLocation(ident.Location(), OpLoadField, c.addConstant(
|
||||
@@ -682,7 +690,7 @@ func (c *compiler) MemberNode(node *ast.MemberNode) {
|
||||
}
|
||||
|
||||
if member, isMember := base.(*ast.MemberNode); isMember {
|
||||
if ok, memberIndex, name := checker.FieldIndex(env, member); ok {
|
||||
if ok, memberIndex, name := checker.FieldIndex(c.ntCache, env, member); ok {
|
||||
index = append(memberIndex, index...)
|
||||
path = append([]string{name}, path...)
|
||||
node = member
|
||||
@@ -743,7 +751,7 @@ func (c *compiler) CallNode(node *ast.CallNode) {
|
||||
}
|
||||
}
|
||||
case *ast.IdentifierNode:
|
||||
if t, ok := c.config.Env.MethodByName(callee.Value); ok && t.Method {
|
||||
if t, ok := c.config.Env.MethodByName(c.ntCache, callee.Value); ok && t.Method {
|
||||
fnInOffset = 1
|
||||
fnNumIn--
|
||||
}
|
||||
@@ -777,7 +785,7 @@ func (c *compiler) CallNode(node *ast.CallNode) {
|
||||
c.compile(node.Callee)
|
||||
|
||||
if c.config != nil {
|
||||
isMethod, _, _ := checker.MethodIndex(c.config.Env, node.Callee)
|
||||
isMethod, _, _ := checker.MethodIndex(c.ntCache, c.config.Env, node.Callee)
|
||||
if index, ok := checker.TypedFuncIndex(node.Callee.Type(), isMethod); ok {
|
||||
c.emit(OpCallTyped, index)
|
||||
return
|
||||
@@ -1080,7 +1088,8 @@ func (c *compiler) BuiltinNode(node *ast.BuiltinNode) {
|
||||
for i, arg := range node.Arguments {
|
||||
c.compile(arg)
|
||||
argType := arg.Type()
|
||||
if argType.Kind() == reflect.Ptr || arg.Nature().IsUnknown() {
|
||||
argNature := arg.Nature()
|
||||
if argType.Kind() == reflect.Ptr || argNature.IsUnknown(c.ntCache) {
|
||||
if f.Deref == nil {
|
||||
// By default, builtins expect arguments to be dereferenced.
|
||||
c.emit(OpDeref)
|
||||
|
||||
+3
-2
@@ -34,6 +34,7 @@ type Config struct {
|
||||
Functions FunctionsTable
|
||||
Builtins FunctionsTable
|
||||
Disabled map[string]bool // disabled builtins
|
||||
NtCache nature.Cache
|
||||
}
|
||||
|
||||
// CreateNew creates new config with default values.
|
||||
@@ -61,7 +62,7 @@ func New(env any) *Config {
|
||||
|
||||
func (c *Config) WithEnv(env any) {
|
||||
c.EnvObject = env
|
||||
c.Env = Env(env)
|
||||
c.Env = EnvWithCache(&c.NtCache, env)
|
||||
c.Strict = c.Env.Strict
|
||||
}
|
||||
|
||||
@@ -92,7 +93,7 @@ func (c *Config) IsOverridden(name string) bool {
|
||||
if _, ok := c.Functions[name]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := c.Env.Get(name); ok {
|
||||
if _, ok := c.Env.Get(&c.NtCache, name); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
+26
-18
@@ -9,35 +9,42 @@ import (
|
||||
"github.com/expr-lang/expr/types"
|
||||
)
|
||||
|
||||
// Env returns the Nature of the given environment.
|
||||
//
|
||||
// Deprecated: use EnvWithCache instead.
|
||||
func Env(env any) Nature {
|
||||
return EnvWithCache(new(Cache), env)
|
||||
}
|
||||
|
||||
func EnvWithCache(c *Cache, env any) Nature {
|
||||
if env == nil {
|
||||
return Nature{
|
||||
Type: reflect.TypeOf(map[string]any{}),
|
||||
Strict: true,
|
||||
}
|
||||
n := c.NatureOf(map[string]any{})
|
||||
n.Strict = true
|
||||
return n
|
||||
}
|
||||
|
||||
switch env := env.(type) {
|
||||
case types.Map:
|
||||
return env.Nature()
|
||||
nt := env.Nature()
|
||||
return nt
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(env)
|
||||
d := deref.Value(v)
|
||||
t := v.Type()
|
||||
|
||||
switch d.Kind() {
|
||||
switch deref.Value(v).Kind() {
|
||||
case reflect.Struct:
|
||||
return Nature{
|
||||
Type: v.Type(),
|
||||
Strict: true,
|
||||
}
|
||||
n := c.FromType(t)
|
||||
n.Strict = true
|
||||
return n
|
||||
|
||||
case reflect.Map:
|
||||
n := Nature{
|
||||
Type: v.Type(),
|
||||
Fields: make(map[string]Nature, v.Len()),
|
||||
Strict: true,
|
||||
n := c.FromType(v.Type())
|
||||
if n.TypeData == nil {
|
||||
n.TypeData = new(TypeData)
|
||||
}
|
||||
n.Strict = true
|
||||
n.Fields = make(map[string]Nature, v.Len())
|
||||
|
||||
for _, key := range v.MapKeys() {
|
||||
elem := v.MapIndex(key)
|
||||
@@ -49,14 +56,15 @@ func Env(env any) Nature {
|
||||
|
||||
switch face := face.(type) {
|
||||
case types.Map:
|
||||
n.Fields[key.String()] = face.Nature()
|
||||
nt := face.Nature()
|
||||
n.Fields[key.String()] = nt
|
||||
|
||||
default:
|
||||
if face == nil {
|
||||
n.Fields[key.String()] = Nature{Nil: true}
|
||||
n.Fields[key.String()] = c.NatureOf(nil)
|
||||
continue
|
||||
}
|
||||
n.Fields[key.String()] = Nature{Type: reflect.TypeOf(face)}
|
||||
n.Fields[key.String()] = c.NatureOf(face)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-2
@@ -85,7 +85,9 @@ func CreateDoc(i any) *Context {
|
||||
PkgPath: deref.Type(reflect.TypeOf(i)).PkgPath(),
|
||||
}
|
||||
|
||||
for name, t := range conf.Env(i).All() {
|
||||
cache := new(nature.Cache)
|
||||
env := conf.EnvWithCache(cache, i)
|
||||
for name, t := range env.All(cache) {
|
||||
if _, ok := c.Variables[Identifier(name)]; ok {
|
||||
continue
|
||||
}
|
||||
@@ -221,7 +223,8 @@ appendix:
|
||||
c.Types[name] = a
|
||||
}
|
||||
|
||||
for name, field := range nature.StructFields(t) {
|
||||
ntCache := new(nature.Cache)
|
||||
for name, field := range nature.StructFields(ntCache, t) {
|
||||
if isPrivate(name) || isProtobuf(name) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ func Operator(operator string, fn ...string) Option {
|
||||
Overloads: fn,
|
||||
Env: &c.Env,
|
||||
Functions: c.Functions,
|
||||
NtCache: &c.NtCache,
|
||||
}
|
||||
c.Visitors = append(c.Visitors, p)
|
||||
}
|
||||
|
||||
@@ -45,3 +45,12 @@ func Value(v reflect.Value) reflect.Value {
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func TypeKind(t reflect.Type, k reflect.Kind) (_ reflect.Type, _ reflect.Kind, changed bool) {
|
||||
for k == reflect.Pointer {
|
||||
changed = true
|
||||
t = t.Elem()
|
||||
k = t.Kind()
|
||||
}
|
||||
return t, k, changed
|
||||
}
|
||||
|
||||
@@ -92,13 +92,6 @@ func (l *Lexer) peek() rune {
|
||||
return eof
|
||||
}
|
||||
|
||||
func (l *Lexer) peekByte() (byte, bool) {
|
||||
if l.end.byte >= 0 && l.end.byte < len(l.source.String()) {
|
||||
return l.source.String()[l.end.byte], true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (l *Lexer) backup() {
|
||||
if l.eof {
|
||||
l.eof = false
|
||||
|
||||
+2
-3
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/expr-lang/expr/builtin"
|
||||
"github.com/expr-lang/expr/conf"
|
||||
"github.com/expr-lang/expr/file"
|
||||
"github.com/expr-lang/expr/parser/lexer"
|
||||
. "github.com/expr-lang/expr/parser/lexer"
|
||||
"github.com/expr-lang/expr/parser/operator"
|
||||
"github.com/expr-lang/expr/parser/utils"
|
||||
@@ -49,7 +48,7 @@ var predicates = map[string]struct {
|
||||
|
||||
// Parser is a reusable parser. The zero value is ready for use.
|
||||
type Parser struct {
|
||||
lexer *lexer.Lexer
|
||||
lexer *Lexer
|
||||
current, stashed Token
|
||||
hasStash bool
|
||||
err *file.Error
|
||||
@@ -60,7 +59,7 @@ type Parser struct {
|
||||
|
||||
func (p *Parser) Parse(input string, config *conf.Config) (*Tree, error) {
|
||||
if p.lexer == nil {
|
||||
p.lexer = lexer.New()
|
||||
p.lexer = New()
|
||||
}
|
||||
p.config = config
|
||||
source := file.NewSource(input)
|
||||
|
||||
@@ -16,6 +16,7 @@ type OperatorOverloading struct {
|
||||
Env *nature.Nature // Env type.
|
||||
Functions conf.FunctionsTable // Env functions.
|
||||
applied bool // Flag to indicate if any changes were made to the tree.
|
||||
NtCache *nature.Cache
|
||||
}
|
||||
|
||||
func (p *OperatorOverloading) Visit(node *ast.Node) {
|
||||
@@ -62,7 +63,7 @@ func (p *OperatorOverloading) FindSuitableOperatorOverload(l, r reflect.Type) (r
|
||||
|
||||
func (p *OperatorOverloading) findSuitableOperatorOverloadInTypes(l, r reflect.Type) (reflect.Type, string, bool) {
|
||||
for _, fn := range p.Overloads {
|
||||
fnType, ok := p.Env.Get(fn)
|
||||
fnType, ok := p.Env.Get(p.NtCache, fn)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -109,7 +110,7 @@ func checkTypeSuits(t reflect.Type, l reflect.Type, r reflect.Type, firstInIndex
|
||||
|
||||
func (p *OperatorOverloading) Check() {
|
||||
for _, fn := range p.Overloads {
|
||||
fnType, foundType := p.Env.Get(fn)
|
||||
fnType, foundType := p.Env.Get(p.NtCache, fn)
|
||||
fnFunc, foundFunc := p.Functions[fn]
|
||||
if !foundFunc && (!foundType || fnType.Type.Kind() != reflect.Func) {
|
||||
panic(fmt.Errorf("function %s for %s operator does not exist in the environment", fn, p.Operator))
|
||||
|
||||
+14
-11
@@ -44,7 +44,7 @@ func TypeOf(v any) Type {
|
||||
type anyType struct{}
|
||||
|
||||
func (anyType) Nature() Nature {
|
||||
return Nature{Type: nil}
|
||||
return FromType(nil)
|
||||
}
|
||||
|
||||
func (anyType) Equal(t Type) bool {
|
||||
@@ -58,7 +58,7 @@ func (anyType) String() string {
|
||||
type nilType struct{}
|
||||
|
||||
func (nilType) Nature() Nature {
|
||||
return Nature{Nil: true}
|
||||
return NatureOf(nil)
|
||||
}
|
||||
|
||||
func (nilType) Equal(t Type) bool {
|
||||
@@ -77,7 +77,7 @@ type rtype struct {
|
||||
}
|
||||
|
||||
func (r rtype) Nature() Nature {
|
||||
return Nature{Type: r.t}
|
||||
return FromType(r.t)
|
||||
}
|
||||
|
||||
func (r rtype) Equal(t Type) bool {
|
||||
@@ -100,11 +100,12 @@ type Map map[string]Type
|
||||
const Extra = "[[__extra_keys__]]"
|
||||
|
||||
func (m Map) Nature() Nature {
|
||||
nt := Nature{
|
||||
Type: reflect.TypeOf(map[string]any{}),
|
||||
Fields: make(map[string]Nature, len(m)),
|
||||
Strict: true,
|
||||
nt := NatureOf(map[string]any{})
|
||||
if nt.TypeData == nil {
|
||||
nt.TypeData = new(TypeData)
|
||||
}
|
||||
nt.Fields = make(map[string]Nature, len(m))
|
||||
nt.Strict = true
|
||||
for k, v := range m {
|
||||
if k == Extra {
|
||||
nt.Strict = false
|
||||
@@ -155,11 +156,13 @@ type array struct {
|
||||
|
||||
func (a array) Nature() Nature {
|
||||
of := a.of.Nature()
|
||||
return Nature{
|
||||
Type: reflect.TypeOf([]any{}),
|
||||
Fields: make(map[string]Nature, 1),
|
||||
ArrayOf: &of,
|
||||
nt := NatureOf([]any{})
|
||||
if nt.TypeData == nil {
|
||||
nt.TypeData = new(TypeData)
|
||||
}
|
||||
nt.Fields = make(map[string]Nature, 1)
|
||||
nt.Ref = &of
|
||||
return nt
|
||||
}
|
||||
|
||||
func (a array) Equal(t Type) bool {
|
||||
|
||||
Reference in New Issue
Block a user