Files
moxa/interp/typecheck.go
Nicholas Wiersma 1029d102e5 feat: add slice expression type checking
This adds type checking to SliceExpr as well as handling any required constant type conversion.

It should be noted that the check that `high` and `max` are not nil in a 3-index slice has been left out as this check is handled before type checking. Tests have been included for these cases.
2020-08-19 16:04:05 +02:00

910 lines
22 KiB
Go

package interp
import (
"errors"
"go/constant"
"math"
"reflect"
)
type opPredicates map[action]func(reflect.Type) bool
// typecheck handles all type checking following "go/types" logic.
//
// Due to variant type systems (itype vs reflect.Type) a single
// type system should used, namely reflect.Type with exception
// of the untyped flag on itype.
type typecheck struct{}
// op type checks an expression against a set of expression predicates.
func (check typecheck) op(p opPredicates, a action, n, c *node, t reflect.Type) error {
if pred := p[a]; pred != nil {
if !pred(t) {
return n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, c.typ.id())
}
} else {
return n.cfgErrorf("invalid operation: unknown operator %v", n.action)
}
return nil
}
// assignment checks if n can be assigned to typ.
//
// Use typ == nil to indicate assignment to an untyped blank identifier.
func (check typecheck) assignment(n *node, typ *itype, context string) error {
if n.typ.untyped {
if typ == nil || isInterface(typ) {
if typ == nil && n.typ.cat == nilT {
return n.cfgErrorf("use of untyped nil in %s", context)
}
typ = n.typ.defaultType()
}
if err := check.convertUntyped(n, typ); err != nil {
return err
}
}
if typ == nil {
return nil
}
if !n.typ.assignableTo(typ) {
if context == "" {
return n.cfgErrorf("cannot use type %s as type %s", n.typ.id(), typ.id())
}
return n.cfgErrorf("cannot use type %s as type %s in %s", n.typ.id(), typ.id(), context)
}
return nil
}
// assignExpr type checks an assign expression.
//
// This is done per pair of assignments.
func (check typecheck) assignExpr(n, dest, src *node) error {
if n.action == aAssign {
isConst := n.anc.kind == constDecl
if !isConst {
// var operations must be typed
dest.typ = dest.typ.defaultType()
}
return check.assignment(src, dest.typ, "assignment")
}
// assignment operations.
if n.nleft > 1 || n.nright > 1 {
return n.cfgErrorf("assignment operation %s requires single-valued expressions", n.action)
}
return check.binaryExpr(n)
}
// addressExpr type checks a unary address expression.
func (check typecheck) addressExpr(n *node) error {
c0 := n.child[0]
found := false
for !found {
switch c0.kind {
case parenExpr:
c0 = c0.child[0]
continue
case selectorExpr:
c0 = c0.child[1]
continue
case indexExpr:
c := c0.child[0]
if isArray(c.typ) || isMap(c.typ) {
c0 = c
continue
}
case compositeLitExpr, identExpr:
found = true
continue
}
return n.cfgErrorf("invalid operation: cannot take address of %s", c0.typ.id())
}
return nil
}
var unaryOpPredicates = opPredicates{
aPos: isNumber,
aNeg: isNumber,
aBitNot: isInt,
aNot: isBoolean,
}
// unaryExpr type checks a unary expression.
func (check typecheck) unaryExpr(n *node) error {
c0 := n.child[0]
t0 := c0.typ.TypeOf()
if n.action == aRecv {
if !isChan(c0.typ) {
return n.cfgErrorf("invalid operation: cannot receive from non-channel %s", c0.typ.id())
}
if isSendChan(c0.typ) {
return n.cfgErrorf("invalid operation: cannot receive from send-only channel %s", c0.typ.id())
}
return nil
}
if err := check.op(unaryOpPredicates, n.action, n, c0, t0); err != nil {
return err
}
return nil
}
// shift type checks a shift binary expression.
func (check typecheck) shift(n *node) error {
c0, c1 := n.child[0], n.child[1]
t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()
var v0 constant.Value
if c0.typ.untyped {
v0 = constant.ToInt(c0.rval.Interface().(constant.Value))
c0.rval = reflect.ValueOf(v0)
}
if !(c0.typ.untyped && v0 != nil && v0.Kind() == constant.Int || isInt(t0)) {
return n.cfgErrorf("invalid operation: shift of type %v", c0.typ.id())
}
switch {
case c1.typ.untyped:
if err := check.convertUntyped(c1, &itype{cat: uintT, name: "uint"}); err != nil {
return n.cfgErrorf("invalid operation: shift count type %v, must be integer", c1.typ.id())
}
case isInt(t1):
// nothing to do
default:
return n.cfgErrorf("invalid operation: shift count type %v, must be integer", c1.typ.id())
}
return nil
}
// comparison type checks a comparison binary expression.
func (check typecheck) comparison(n *node) error {
c0, c1 := n.child[0], n.child[1]
if !c0.typ.assignableTo(c1.typ) && !c1.typ.assignableTo(c0.typ) {
return n.cfgErrorf("invalid operation: mismatched types %s and %s", c0.typ.id(), c1.typ.id())
}
ok := false
switch n.action {
case aEqual, aNotEqual:
ok = c0.typ.comparable() && c1.typ.comparable() || c0.typ.isNil() && c1.typ.hasNil() || c1.typ.isNil() && c0.typ.hasNil()
case aLower, aLowerEqual, aGreater, aGreaterEqual:
ok = c0.typ.ordered() && c1.typ.ordered()
}
if !ok {
typ := c0.typ
if typ.isNil() {
typ = c1.typ
}
return n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, typ.id(), ".")
}
return nil
}
var binaryOpPredicates = opPredicates{
aAdd: func(typ reflect.Type) bool { return isNumber(typ) || isString(typ) },
aSub: isNumber,
aMul: isNumber,
aQuo: isNumber,
aRem: isInt,
aAnd: isInt,
aOr: isInt,
aXor: isInt,
aAndNot: isInt,
aLand: isBoolean,
aLor: isBoolean,
}
// binaryExpr type checks a binary expression.
func (check typecheck) binaryExpr(n *node) error {
c0, c1 := n.child[0], n.child[1]
a := n.action
if isAssignAction(a) {
a--
}
if isShiftAction(a) {
return check.shift(n)
}
_ = check.convertUntyped(c0, c1.typ)
_ = check.convertUntyped(c1, c0.typ)
if isComparisonAction(a) {
return check.comparison(n)
}
if !c0.typ.equals(c1.typ) {
return n.cfgErrorf("invalid operation: mismatched types %s and %s", c0.typ.id(), c1.typ.id())
}
t0 := c0.typ.TypeOf()
if err := check.op(binaryOpPredicates, a, n, c0, t0); err != nil {
return err
}
switch n.action {
case aQuo, aRem:
if (c0.typ.untyped || isInt(t0)) && c1.typ.untyped && constant.Sign(c1.rval.Interface().(constant.Value)) == 0 {
return n.cfgErrorf("invalid operation: division by zero")
}
}
return nil
}
func (check typecheck) index(n *node, max int) error {
if err := check.convertUntyped(n, &itype{cat: intT, name: "int"}); err != nil {
return err
}
if !isInt(n.typ.TypeOf()) {
return n.cfgErrorf("index %s must be integer", n.typ.id())
}
if !n.rval.IsValid() || max < 1 {
return nil
}
if int(vInt(n.rval)) >= max {
return n.cfgErrorf("index %s is out of bounds", n.typ.id())
}
return nil
}
// arrayLitExpr type checks an array composite literal expression.
func (check typecheck) arrayLitExpr(child []*node, typ *itype, length int) error {
visited := make(map[int]bool, len(child))
index := 0
for _, c := range child {
n := c
switch {
case c.kind == keyValueExpr:
if err := check.index(c.child[0], length); err != nil {
return c.cfgErrorf("index %s must be integer constant", c.child[0].typ.id())
}
n = c.child[1]
index = int(vInt(c.child[0].rval))
case length > 0 && index >= length:
return c.cfgErrorf("index %d is out of bounds (>= %d)", index, length)
}
if visited[index] {
return n.cfgErrorf("duplicate index %d in array or slice literal", index)
}
visited[index] = true
index++
if err := check.assignment(n, typ, "array or slice literal"); err != nil {
return err
}
}
return nil
}
// mapLitExpr type checks an map composite literal expression.
func (check typecheck) mapLitExpr(child []*node, ktyp, vtyp *itype) error {
visited := make(map[interface{}]bool, len(child))
for _, c := range child {
if c.kind != keyValueExpr {
return c.cfgErrorf("missing key in map literal")
}
key, val := c.child[0], c.child[1]
if err := check.assignment(key, ktyp, "map literal"); err != nil {
return err
}
if key.rval.IsValid() {
kval := key.rval.Interface()
if visited[kval] {
return c.cfgErrorf("duplicate key %s in map literal", kval)
}
visited[kval] = true
}
if err := check.assignment(val, vtyp, "map literal"); err != nil {
return err
}
}
return nil
}
// structLitExpr type checks a struct composite literal expression.
func (check typecheck) structLitExpr(child []*node, typ *itype) error {
if len(child) == 0 {
return nil
}
if child[0].kind == keyValueExpr {
// All children must be keyValueExpr
visited := make([]bool, len(typ.field))
for _, c := range child {
if c.kind != keyValueExpr {
return c.cfgErrorf("mixture of field:value and value elements in struct literal")
}
key, val := c.child[0], c.child[1]
name := key.ident
if name == "" {
return c.cfgErrorf("invalid field name %s in struct literal", key.typ.id())
}
i := typ.fieldIndex(name)
if i < 0 {
return c.cfgErrorf("unknown field %s in struct literal", name)
}
field := typ.field[i]
if err := check.assignment(val, field.typ, "struct literal"); err != nil {
return err
}
if visited[i] {
return c.cfgErrorf("duplicate field name %s in struct literal", name)
}
visited[i] = true
}
return nil
}
// No children can be keyValueExpr
for i, c := range child {
if c.kind == keyValueExpr {
return c.cfgErrorf("mixture of field:value and value elements in struct literal")
}
if i >= len(typ.field) {
return c.cfgErrorf("too many values in struct literal")
}
field := typ.field[i]
// TODO(nick): check if this field is not exported and in a different package.
if err := check.assignment(c, field.typ, "struct literal"); err != nil {
return err
}
}
if len(child) < len(typ.field) {
return child[len(child)-1].cfgErrorf("too few values in struct literal")
}
return nil
}
// structBinLitExpr type checks a struct composite literal expression on a binary type.
func (check typecheck) structBinLitExpr(child []*node, typ reflect.Type) error {
if len(child) == 0 {
return nil
}
if child[0].kind == keyValueExpr {
// All children must be keyValueExpr
visited := make(map[string]bool, typ.NumField())
for _, c := range child {
if c.kind != keyValueExpr {
return c.cfgErrorf("mixture of field:value and value elements in struct literal")
}
key, val := c.child[0], c.child[1]
name := key.ident
if name == "" {
return c.cfgErrorf("invalid field name %s in struct literal", key.typ.id())
}
field, ok := typ.FieldByName(name)
if !ok {
return c.cfgErrorf("unknown field %s in struct literal", name)
}
if err := check.assignment(val, &itype{cat: valueT, rtype: field.Type}, "struct literal"); err != nil {
return err
}
if visited[field.Name] {
return c.cfgErrorf("duplicate field name %s in struct literal", name)
}
visited[field.Name] = true
}
return nil
}
// No children can be keyValueExpr
for i, c := range child {
if c.kind == keyValueExpr {
return c.cfgErrorf("mixture of field:value and value elements in struct literal")
}
if i >= typ.NumField() {
return c.cfgErrorf("too many values in struct literal")
}
field := typ.Field(i)
if !canExport(field.Name) {
return c.cfgErrorf("implicit assignment to unexported field %s in %s literal", field.Name, typ)
}
if err := check.assignment(c, &itype{cat: valueT, rtype: field.Type}, "struct literal"); err != nil {
return err
}
}
if len(child) < typ.NumField() {
return child[len(child)-1].cfgErrorf("too few values in struct literal")
}
return nil
}
// sliceExpr type checks a slice expression.
func (check typecheck) sliceExpr(n *node) error {
c, child := n.child[0], n.child[1:]
t := c.typ.TypeOf()
var low, high, max *node
if len(child) >= 1 {
if n.action == aSlice {
low = child[0]
} else {
high = child[0]
}
}
if len(child) >= 2 {
if n.action == aSlice {
high = child[1]
} else {
max = child[1]
}
}
if len(child) == 3 && n.action == aSlice {
max = child[2]
}
l := -1
valid := false
switch t.Kind() {
case reflect.String:
valid = true
if c.rval.IsValid() {
l = len(vString(c.rval))
}
if max != nil {
return max.cfgErrorf("invalid operation: 3-index slice of string")
}
case reflect.Array:
valid = true
l = t.Len()
if c.kind != selectorExpr && (c.sym == nil || c.sym.kind != varSym) {
return c.cfgErrorf("cannot slice type %s", c.typ.id())
}
case reflect.Slice:
valid = true
case reflect.Ptr:
if t.Elem().Kind() == reflect.Array {
valid = true
l = t.Elem().Len()
}
}
if !valid {
return c.cfgErrorf("cannot slice type %s", c.typ.id())
}
var ind [3]int64
for i, nod := range []*node{low, high, max} {
x := int64(-1)
switch {
case nod != nil:
max := -1
if l >= 0 {
max = l + 1
}
if err := check.index(nod, max); err != nil {
return err
}
if nod.rval.IsValid() {
x = vInt(nod.rval)
}
case i == 0:
x = 0
case l >= 0:
x = int64(l)
}
ind[i] = x
}
for i, x := range ind[:len(ind)-1] {
if x <= 0 {
continue
}
for _, y := range ind[i+1:] {
if y < 0 || x <= y {
continue
}
return n.cfgErrorf("invalid index values, must be low <= high <= max")
}
}
return nil
}
// conversion type checks the conversion of n to typ.
func (check typecheck) conversion(n *node, typ *itype) error {
var c constant.Value
if n.rval.IsValid() {
if con, ok := n.rval.Interface().(constant.Value); ok {
c = con
}
}
var ok bool
switch {
case c != nil && isConstType(typ):
switch t := typ.TypeOf(); {
case representableConst(c, t):
ok = true
case isInt(n.typ.TypeOf()) && isString(t):
codepoint := int64(-1)
if i, ok := constant.Int64Val(c); ok {
codepoint = i
}
n.rval = reflect.ValueOf(constant.MakeString(string(rune(codepoint))))
ok = true
}
case n.typ.convertibleTo(typ):
ok = true
}
if !ok {
return n.cfgErrorf("cannot convert expression of type %s to type %s", n.typ.id(), typ.id())
}
if n.typ.untyped {
if isInterface(typ) || c != nil && !isConstType(typ) {
typ = n.typ.defaultType()
}
if err := check.convertUntyped(n, typ); err != nil {
return err
}
}
return nil
}
// arguments type checks the call expression arguments.
func (check typecheck) arguments(n *node, child []*node, fun *node, ellipsis bool) error {
l := len(child)
if ellipsis {
if !fun.typ.isVariadic() {
return n.cfgErrorf("invalid use of ..., corresponding parameter is non-variadic")
}
if len(child) == 1 && isCall(child[0]) && child[0].child[0].typ.numOut() > 1 {
return child[0].cfgErrorf("cannot use ... with %d-valued %s", child[0].child[0].typ.numOut(), child[0].child[0].typ.id())
}
}
if len(child) == 1 && isCall(child[0]) && child[0].child[0].typ.numOut() > 1 {
// Handle the case of unpacking a n-valued function into the params.
c := child[0].child[0]
l := c.typ.numOut()
if l < fun.typ.numIn() {
return child[0].cfgErrorf("not enough arguments in call to %s", fun.name())
}
for i := 0; i < l; i++ {
arg := getArg(fun.typ, i)
if arg == nil {
return child[0].cfgErrorf("too many arguments")
}
if !c.typ.out(i).assignableTo(arg) {
return child[0].cfgErrorf("cannot use %s as type %s", c.typ.id(), getArgsID(fun.typ))
}
}
return nil
}
var cnt int
if fun.kind == selectorExpr && fun.typ.cat == valueT && fun.recv != nil && !isInterface(fun.recv.node.typ) {
// If this is a bin call, and we have a receiver and the receiver is
// not an interface, then the first input is the receiver, so skip it.
cnt++
}
for i, arg := range child {
ellip := i == l-1 && ellipsis
if err := check.argument(arg, fun.typ, cnt, ellip); err != nil {
return err
}
cnt++
}
if fun.typ.isVariadic() {
cnt++
}
if cnt < fun.typ.numIn() {
return n.cfgErrorf("not enough arguments in call to %s", fun.name())
}
return nil
}
func (check typecheck) argument(n *node, ftyp *itype, i int, ellipsis bool) error {
typ := getArg(ftyp, i)
if typ == nil {
return n.cfgErrorf("too many arguments")
}
if isCall(n) && n.child[0].typ.numOut() != 1 {
return n.cfgErrorf("cannot use %s as type %s", n.child[0].typ.id(), typ.id())
}
if ellipsis {
if i != ftyp.numIn()-1 {
return n.cfgErrorf("can only use ... with matching parameter")
}
t := n.typ.TypeOf()
if t.Kind() != reflect.Slice || !(&itype{cat: valueT, rtype: t.Elem()}).assignableTo(typ) {
return n.cfgErrorf("cannot use %s as type %s", n.typ.id(), (&itype{cat: arrayT, val: typ}).id())
}
return nil
}
err := check.assignment(n, typ, "")
return err
}
func getArg(ftyp *itype, i int) *itype {
l := ftyp.numIn()
switch {
case ftyp.isVariadic() && i >= l-1:
arg := ftyp.in(l - 1).val
return arg
case i < l:
return ftyp.in(i)
default:
return nil
}
}
func getArgsID(ftyp *itype) string {
res := "("
for i, arg := range ftyp.arg {
if i > 0 {
res += ","
}
res += arg.id()
}
res += ")"
return res
}
var errCantConvert = errors.New("cannot convert")
func (check typecheck) convertUntyped(n *node, typ *itype) error {
if n.typ == nil || !n.typ.untyped || typ == nil {
return nil
}
convErr := n.cfgErrorf("cannot convert %s to %s", n.typ.id(), typ.id())
ntyp, ttyp := n.typ.TypeOf(), typ.TypeOf()
if typ.untyped {
// Both n and target are untyped.
nkind, tkind := ntyp.Kind(), ttyp.Kind()
if isNumber(ntyp) && isNumber(ttyp) {
if nkind < tkind {
n.typ = typ
}
} else if nkind != tkind {
return convErr
}
return nil
}
var (
ityp *itype
rtyp reflect.Type
err error
)
switch {
case typ.isNil() && n.typ.isNil():
n.typ = typ
return nil
case isNumber(ttyp) || isString(ttyp) || isBoolean(ttyp):
ityp = typ
rtyp = ttyp
case isInterface(typ):
if n.typ.isNil() {
return nil
}
if len(n.typ.methods()) > 0 { // untyped cannot be set to iface
return convErr
}
ityp = n.typ.defaultType()
rtyp = ntyp
case isArray(typ) || isMap(typ) || isChan(typ) || isFunc(typ) || isPtr(typ):
// TODO(nick): above we are acting on itype, but really it is an rtype check. This is not clear which type
// plain we are in. Fix this later.
if !n.typ.isNil() {
return convErr
}
return nil
default:
return convErr
}
if err := check.representable(n, rtyp); err != nil {
return err
}
n.rval, err = check.convertConst(n.rval, rtyp)
if err != nil {
if errors.Is(err, errCantConvert) {
return convErr
}
return n.cfgErrorf(err.Error())
}
n.typ = ityp
return nil
}
func (check typecheck) representable(n *node, t reflect.Type) error {
if !n.rval.IsValid() {
// TODO(nick): This should be an error as the const is in the frame which is undesirable.
return nil
}
c, ok := n.rval.Interface().(constant.Value)
if !ok {
// TODO(nick): This should be an error as untyped strings and bools should be constant.Values.
return nil
}
if !representableConst(c, t) {
typ := n.typ.TypeOf()
if isNumber(typ) && isNumber(t) {
// numeric conversion : error msg
//
// integer -> integer : overflows
// integer -> float : overflows (actually not possible)
// float -> integer : truncated
// float -> float : overflows
//
if !isInt(typ) && isInt(t) {
return n.cfgErrorf("%s truncated to %s", c.ExactString(), t.Kind().String())
}
return n.cfgErrorf("%s overflows %s", c.ExactString(), t.Kind().String())
}
return n.cfgErrorf("cannot convert %s to %s", c.ExactString(), t.Kind().String())
}
return nil
}
func (check typecheck) convertConst(v reflect.Value, t reflect.Type) (reflect.Value, error) {
if !v.IsValid() {
// TODO(nick): This should be an error as the const is in the frame which is undesirable.
return v, nil
}
c, ok := v.Interface().(constant.Value)
if !ok {
// TODO(nick): This should be an error as untyped strings and bools should be constant.Values.
return v, nil
}
kind := t.Kind()
switch kind {
case reflect.Bool:
v = reflect.ValueOf(constant.BoolVal(c))
case reflect.String:
v = reflect.ValueOf(constant.StringVal(c))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i, _ := constant.Int64Val(constant.ToInt(c))
v = reflect.ValueOf(i).Convert(t)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
i, _ := constant.Uint64Val(constant.ToInt(c))
v = reflect.ValueOf(i).Convert(t)
case reflect.Float32:
f, _ := constant.Float32Val(constant.ToFloat(c))
v = reflect.ValueOf(f)
case reflect.Float64:
f, _ := constant.Float64Val(constant.ToFloat(c))
v = reflect.ValueOf(f)
case reflect.Complex64:
r, _ := constant.Float32Val(constant.Real(c))
i, _ := constant.Float32Val(constant.Imag(c))
v = reflect.ValueOf(complex(r, i)).Convert(t)
case reflect.Complex128:
r, _ := constant.Float64Val(constant.Real(c))
i, _ := constant.Float64Val(constant.Imag(c))
v = reflect.ValueOf(complex(r, i)).Convert(t)
default:
return v, errCantConvert
}
return v, nil
}
var bitlen = [...]int{
reflect.Int: 64,
reflect.Int8: 8,
reflect.Int16: 16,
reflect.Int32: 32,
reflect.Int64: 64,
reflect.Uint: 64,
reflect.Uint8: 8,
reflect.Uint16: 16,
reflect.Uint32: 32,
reflect.Uint64: 64,
reflect.Uintptr: 64,
}
func representableConst(c constant.Value, t reflect.Type) bool {
switch {
case isInt(t):
x := constant.ToInt(c)
if x.Kind() != constant.Int {
return false
}
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if _, ok := constant.Int64Val(x); !ok {
return false
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
if _, ok := constant.Uint64Val(x); !ok {
return false
}
default:
return false
}
return constant.BitLen(x) <= bitlen[t.Kind()]
case isFloat(t):
x := constant.ToFloat(c)
if x.Kind() != constant.Float {
return false
}
switch t.Kind() {
case reflect.Float32:
f, _ := constant.Float32Val(x)
return !math.IsInf(float64(f), 0)
case reflect.Float64:
f, _ := constant.Float64Val(x)
return !math.IsInf(f, 0)
default:
return false
}
case isComplex(t):
x := constant.ToComplex(c)
if x.Kind() != constant.Complex {
return false
}
switch t.Kind() {
case reflect.Complex64:
r, _ := constant.Float32Val(constant.Real(x))
i, _ := constant.Float32Val(constant.Imag(x))
return !math.IsInf(float64(r), 0) && !math.IsInf(float64(i), 0)
case reflect.Complex128:
r, _ := constant.Float64Val(constant.Real(x))
i, _ := constant.Float64Val(constant.Imag(x))
return !math.IsInf(r, 0) && !math.IsInf(i, 0)
default:
return false
}
case isString(t):
return c.Kind() == constant.String
case isBoolean(t):
return c.Kind() == constant.Bool
default:
return false
}
}
func isShiftAction(a action) bool {
switch a {
case aShl, aShr, aShlAssign, aShrAssign:
return true
}
return false
}
func isComparisonAction(a action) bool {
switch a {
case aEqual, aNotEqual, aGreater, aGreaterEqual, aLower, aLowerEqual:
return true
}
return false
}