		default:
			if nt.Kind != v.config.Expect {
				return nil, fmt.Errorf("expected %v, but got %s", v.config.Expect, nt.String())
			}
		}
	}

	return t, nil
}

func (v *Checker) reset(config *conf.Config) {
	if v.needsReset {
		clearSlice(v.predicateScopes)
		clearSlice(v.varScopes)
		v.predicateScopes = v.predicateScopes[:0]
		v.varScopes = v.varScopes[:0]
		v.err = nil
	}
	v.needsReset = true

	if config == nil {
		config = conf.New(nil)
	}
	v.config = config
}

func clearSlice[S ~[]E, E any](s S) {
	var zero E
	for i := range s {
		s[i] = zero
	}
}

func (v *Checker) visit(node ast.Node) Nature {
	var nt Nature
	switch n := node.(type) {
	case *ast.NilNode:
		nt = v.config.NtCache.NatureOf(nil)
	case *ast.IdentifierNode:
		nt = v.identifierNode(n)
	case *ast.IntegerNode:
		nt = v.config.NtCache.FromType(intType)
	case *ast.FloatNode:
		nt = v.config.NtCache.FromType(floatType)
	case *ast.BoolNode:
		nt = v.config.NtCache.FromType(boolType)
	case *ast.StringNode:
		nt = v.config.NtCache.FromType(stringType)
	case *ast.BytesNode:
		nt = v.config.NtCache.FromType(byteSliceType)
	case *ast.ConstantNode:
		nt = v.config.NtCache.FromType(reflect.TypeOf(n.Value))
	case *ast.UnaryNode:
		nt = v.unaryNode(n)
	case *ast.BinaryNode:
		nt = v.binaryNode(n)
	case *ast.ChainNode:
		nt = v.chainNode(n)
	case *ast.MemberNode:
		nt = v.memberNode(n)
	case *ast.SliceNode:
		nt = v.sliceNode(n)
	case *ast.CallNode:
		nt = v.callNode(n)
	case *ast.BuiltinNode:
		nt = v.builtinNode(n)
	case *ast.PredicateNode:
		nt = v.predicateNode(n)
	case *ast.PointerNode:
		nt = v.pointerNode(n)
	case *ast.VariableDeclaratorNode:
		nt = v.variableDeclaratorNode(n)
	case *ast.SequenceNode:
		nt = v.sequenceNode(n)
	case *ast.ConditionalNode:
		nt = v.conditionalNode(n)
	case *ast.ArrayNode:
		nt = v.arrayNode(n)
	case *ast.MapNode:
		nt = v.mapNode(n)
	case *ast.PairNode:
		nt = v.pairNode(n)
	default:
		panic(fmt.Sprintf("undefined node type (%T)", node))
	}
	node.SetNature(nt)
	return nt
}

func (v *Checker) error(node ast.Node, format string, args ...any) Nature {
	if v.err == nil { // show first error
		v.err = &file.Error{
			Location: node.Location(),
			Message:  fmt.Sprintf(format, args...),
		}
	}
	return Nature{}
}

func (v *Checker) identifierNode(node *ast.IdentifierNode) Nature {
	for i := len(v.varScopes) - 1; i >= 0; i-- {
		if v.varScopes[i].name == node.Value {
			return v.varScopes[i].nature
		}
	}
	if node.Value == "$env" {
		return Nature{}
	}

	return v.ident(node, node.Value, v.config.Strict, true)
}

// ident method returns type of environment variable, builtin or function.
func (v *Checker) ident(node ast.Node, name string, strict, builtins bool) Nature {
	if nt, ok := v.config.Env.Get(&v.config.NtCache, name); ok {
		return nt
	}
	if builtins {
		if fn, ok := v.config.Functions[name]; ok {
			nt := v.config.NtCache.FromType(fn.Type())
			if nt.TypeData == nil {
				nt.TypeData = new(TypeData)
			}
			nt.TypeData.Func = fn
			return nt
		}
		if fn, ok := v.config.Builtins[name]; ok {
			nt := v.config.NtCache.FromType(fn.Type())
			if nt.TypeData == nil {
				nt.TypeData = new(TypeData)
			}
			nt.TypeData.Func = fn
			return nt
		}
	}
	if v.config.Strict && strict {
		return v.error(node, "unknown name %s", name)
	}
	return Nature{}
}

func (v *Checker) unaryNode(node *ast.UnaryNode) Nature {
	nt := v.visit(node.Node)
	nt = nt.Deref(&v.config.NtCache)

	switch node.Operator {

	case "!", "not":
		if nt.IsBool() {
			return v.config.NtCache.FromType(boolType)
		}
		if nt.IsUnknown(&v.config.NtCache) {
			return v.config.NtCache.FromType(boolType)
		}

	case "+", "-":
		if nt.IsNumber() {
			return nt
		}
		if nt.IsUnknown(&v.config.NtCache) {
			return Nature{}
		}

	default:
		return v.error(node, "unknown operator (%s)", node.Operator)
	}

	return v.error(node, `invalid operation: %s (mismatched type %s)`, node.Operator, nt.String())
}

func (v *Checker) binaryNode(node *ast.BinaryNode) Nature {
