]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.typeparams] go/types: import predicates.go from dev.go2go
authorRob Findley <rfindley@google.com>
Tue, 15 Dec 2020 23:38:53 +0000 (18:38 -0500)
committerRobert Findley <rfindley@google.com>
Wed, 16 Dec 2020 17:09:45 +0000 (17:09 +0000)
Changes from dev.go2go:
 + Update some isComparable cases to use the seen map.
 + Tiny updates to comments.

Change-Id: Iafd85d60835f17a87f514d9774cae07c183ee6cc
Reviewed-on: https://go-review.googlesource.com/c/go/+/278594
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
src/go/types/expr.go
src/go/types/predicates.go

index 51714790cbbf04d2ddb471da05d0f4600f7dcff9..c57edf8f0d37ae847aa1ce8aca03804c7ecd0301 100644 (file)
@@ -58,11 +58,16 @@ the type (and constant value, if any) is recorded via Info.Types, if present.
 
 type opPredicates map[token.Token]func(Type) bool
 
-var unaryOpPredicates = opPredicates{
-       token.ADD: isNumeric,
-       token.SUB: isNumeric,
-       token.XOR: isInteger,
-       token.NOT: isBoolean,
+var unaryOpPredicates opPredicates
+
+func init() {
+       // Setting unaryOpPredicates in init avoids declaration cycles.
+       unaryOpPredicates = opPredicates{
+               token.ADD: isNumeric,
+               token.SUB: isNumeric,
+               token.XOR: isInteger,
+               token.NOT: isBoolean,
+       }
 }
 
 func (check *Checker) op(m opPredicates, x *operand, op token.Token) bool {
@@ -785,20 +790,25 @@ func (check *Checker) shift(x, y *operand, e *ast.BinaryExpr, op token.Token) {
        x.mode = value
 }
 
-var binaryOpPredicates = opPredicates{
-       token.ADD: func(typ Type) bool { return isNumeric(typ) || isString(typ) },
-       token.SUB: isNumeric,
-       token.MUL: isNumeric,
-       token.QUO: isNumeric,
-       token.REM: isInteger,
+var binaryOpPredicates opPredicates
+
+func init() {
+       // Setting binaryOpPredicates in init avoids declaration cycles.
+       binaryOpPredicates = opPredicates{
+               token.ADD: isNumericOrString,
+               token.SUB: isNumeric,
+               token.MUL: isNumeric,
+               token.QUO: isNumeric,
+               token.REM: isInteger,
 
-       token.AND:     isInteger,
-       token.OR:      isInteger,
-       token.XOR:     isInteger,
-       token.AND_NOT: isInteger,
+               token.AND:     isInteger,
+               token.OR:      isInteger,
+               token.XOR:     isInteger,
+               token.AND_NOT: isInteger,
 
-       token.LAND: isBoolean,
-       token.LOR:  isBoolean,
+               token.LAND: isBoolean,
+               token.LOR:  isBoolean,
+       }
 }
 
 // The binary expression e may be nil. It's passed in for better error messages only.
index d580796d9890b58da45e12f8f997c00ba6d78662..85e2b9a0cad48b9d0e8b7067423e42dca46a6b78 100644 (file)
@@ -11,73 +11,79 @@ import (
        "sort"
 )
 
+// isNamed reports whether typ has a name.
+// isNamed may be called with types that are not fully set up.
 func isNamed(typ Type) bool {
-       if _, ok := typ.(*Basic); ok {
-               return ok
+       switch typ.(type) {
+       case *Basic, *Named, *TypeParam, *instance:
+               return true
        }
-       _, ok := typ.(*Named)
-       return ok
-}
-
-func isBoolean(typ Type) bool {
-       t, ok := typ.Underlying().(*Basic)
-       return ok && t.info&IsBoolean != 0
-}
-
-func isInteger(typ Type) bool {
-       t, ok := typ.Underlying().(*Basic)
-       return ok && t.info&IsInteger != 0
-}
-
-func isUnsigned(typ Type) bool {
-       t, ok := typ.Underlying().(*Basic)
-       return ok && t.info&IsUnsigned != 0
-}
-
-func isFloat(typ Type) bool {
-       t, ok := typ.Underlying().(*Basic)
-       return ok && t.info&IsFloat != 0
-}
-
-func isComplex(typ Type) bool {
-       t, ok := typ.Underlying().(*Basic)
-       return ok && t.info&IsComplex != 0
+       return false
 }
 
-func isNumeric(typ Type) bool {
-       t, ok := typ.Underlying().(*Basic)
-       return ok && t.info&IsNumeric != 0
+// isGeneric reports whether a type is a generic, uninstantiated type (generic
+// signatures are not included).
+func isGeneric(typ Type) bool {
+       // A parameterized type is only instantiated if it doesn't have an instantiation already.
+       named, _ := typ.(*Named)
+       return named != nil && named.obj != nil && named.tparams != nil && named.targs == nil
 }
 
-func isString(typ Type) bool {
-       t, ok := typ.Underlying().(*Basic)
-       return ok && t.info&IsString != 0
+func is(typ Type, what BasicInfo) bool {
+       switch t := optype(typ).(type) {
+       case *Basic:
+               return t.info&what != 0
+       case *Sum:
+               return t.is(func(typ Type) bool { return is(typ, what) })
+       }
+       return false
 }
 
+func isBoolean(typ Type) bool  { return is(typ, IsBoolean) }
+func isInteger(typ Type) bool  { return is(typ, IsInteger) }
+func isUnsigned(typ Type) bool { return is(typ, IsUnsigned) }
+func isFloat(typ Type) bool    { return is(typ, IsFloat) }
+func isComplex(typ Type) bool  { return is(typ, IsComplex) }
+func isNumeric(typ Type) bool  { return is(typ, IsNumeric) }
+func isString(typ Type) bool   { return is(typ, IsString) }
+
+// Note that if typ is a type parameter, isInteger(typ) || isFloat(typ) does not
+// produce the expected result because a type list that contains both an integer
+// and a floating-point type is neither (all) integers, nor (all) floats.
+// Use isIntegerOrFloat instead.
+func isIntegerOrFloat(typ Type) bool { return is(typ, IsInteger|IsFloat) }
+
+// isNumericOrString is the equivalent of isIntegerOrFloat for isNumeric(typ) || isString(typ).
+func isNumericOrString(typ Type) bool { return is(typ, IsNumeric|IsString) }
+
+// isTyped reports whether typ is typed; i.e., not an untyped
+// constant or boolean. isTyped may be called with types that
+// are not fully set up.
 func isTyped(typ Type) bool {
-       t, ok := typ.Underlying().(*Basic)
-       return !ok || t.info&IsUntyped == 0
+       // isTyped is called with types that are not fully
+       // set up. Must not call asBasic()!
+       // A *Named or *instance type is always typed, so
+       // we only need to check if we have a true *Basic
+       // type.
+       t, _ := typ.(*Basic)
+       return t == nil || t.info&IsUntyped == 0
 }
 
+// isUntyped(typ) is the same as !isTyped(typ).
 func isUntyped(typ Type) bool {
-       t, ok := typ.Underlying().(*Basic)
-       return ok && t.info&IsUntyped != 0
+       return !isTyped(typ)
 }
 
-func isOrdered(typ Type) bool {
-       t, ok := typ.Underlying().(*Basic)
-       return ok && t.info&IsOrdered != 0
-}
+func isOrdered(typ Type) bool { return is(typ, IsOrdered) }
 
 func isConstType(typ Type) bool {
-       t, ok := typ.Underlying().(*Basic)
-       return ok && t.info&IsConstType != 0
+       t := asBasic(typ)
+       return t != nil && t.info&IsConstType != 0
 }
 
 // IsInterface reports whether typ is an interface type.
 func IsInterface(typ Type) bool {
-       _, ok := typ.Underlying().(*Interface)
-       return ok
+       return asInterface(typ) != nil
 }
 
 // Comparable reports whether values of type T are comparable.
@@ -94,7 +100,19 @@ func comparable(T Type, seen map[Type]bool) bool {
        }
        seen[T] = true
 
-       switch t := T.Underlying().(type) {
+       // If T is a type parameter not constrained by any type
+       // list (i.e., it's underlying type is the top type),
+       // T is comparable if it has the == method. Otherwise,
+       // the underlying type "wins". For instance
+       //
+       //     interface{ comparable; type []byte }
+       //
+       // is not comparable because []byte is not comparable.
+       if t := asTypeParam(T); t != nil && optype(t) == theTop {
+               return t.Bound().IsComparable()
+       }
+
+       switch t := optype(T).(type) {
        case *Basic:
                // assume invalid types to be comparable
                // to avoid follow-up errors
@@ -110,17 +128,26 @@ func comparable(T Type, seen map[Type]bool) bool {
                return true
        case *Array:
                return comparable(t.elem, seen)
+       case *Sum:
+               pred := func(t Type) bool {
+                       return comparable(t, seen)
+               }
+               return t.is(pred)
+       case *TypeParam:
+               return t.Bound().IsComparable()
        }
        return false
 }
 
 // hasNil reports whether a type includes the nil value.
 func hasNil(typ Type) bool {
-       switch t := typ.Underlying().(type) {
+       switch t := optype(typ).(type) {
        case *Basic:
                return t.kind == UnsafePointer
        case *Slice, *Pointer, *Signature, *Interface, *Map, *Chan:
                return true
+       case *Sum:
+               return t.is(hasNil)
        }
        return false
 }
@@ -147,7 +174,12 @@ func (p *ifacePair) identical(q *ifacePair) bool {
        return p.x == q.x && p.y == q.y || p.x == q.y && p.y == q.x
 }
 
+// For changes to this code the corresponding changes should be made to unifier.nify.
 func (check *Checker) identical0(x, y Type, cmpTags bool, p *ifacePair) bool {
+       // types must be expanded for comparison
+       x = expandf(x)
+       y = expandf(y)
+
        if x == y {
                return true
        }
@@ -224,12 +256,38 @@ func (check *Checker) identical0(x, y Type, cmpTags bool, p *ifacePair) bool {
                // and result values, corresponding parameter and result types are identical,
                // and either both functions are variadic or neither is. Parameter and result
                // names are not required to match.
+               // Generic functions must also have matching type parameter lists, but for the
+               // parameter names.
                if y, ok := y.(*Signature); ok {
                        return x.variadic == y.variadic &&
+                               check.identicalTParams(x.tparams, y.tparams, cmpTags, p) &&
                                check.identical0(x.params, y.params, cmpTags, p) &&
                                check.identical0(x.results, y.results, cmpTags, p)
                }
 
+       case *Sum:
+               // Two sum types are identical if they contain the same types.
+               // (Sum types always consist of at least two types. Also, the
+               // the set (list) of types in a sum type consists of unique
+               // types - each type appears exactly once. Thus, two sum types
+               // must contain the same number of types to have chance of
+               // being equal.
+               if y, ok := y.(*Sum); ok && len(x.types) == len(y.types) {
+                       // Every type in x.types must be in y.types.
+                       // Quadratic algorithm, but probably good enough for now.
+                       // TODO(gri) we need a fast quick type ID/hash for all types.
+               L:
+                       for _, x := range x.types {
+                               for _, y := range y.types {
+                                       if Identical(x, y) {
+                                               continue L // x is in y.types
+                                       }
+                               }
+                               return false // x is not in y.types
+                       }
+                       return true
+               }
+
        case *Interface:
                // Two interface types are identical if they have the same set of methods with
                // the same names and identical function types. Lower-case method names from
@@ -306,10 +364,25 @@ func (check *Checker) identical0(x, y Type, cmpTags bool, p *ifacePair) bool {
                // Two named types are identical if their type names originate
                // in the same type declaration.
                if y, ok := y.(*Named); ok {
+                       // TODO(gri) Why is x == y not sufficient? And if it is,
+                       //           we can just return false here because x == y
+                       //           is caught in the very beginning of this function.
                        return x.obj == y.obj
                }
 
+       case *TypeParam:
+               // nothing to do (x and y being equal is caught in the very beginning of this function)
+
+       // case *instance:
+       //      unreachable since types are expanded
+
+       case *bottom, *top:
+               // Either both types are theBottom, or both are theTop in which
+               // case the initial x == y check will have caught them. Otherwise
+               // they are not identical.
+
        case nil:
+               // avoid a crash in case of nil type
 
        default:
                unreachable()
@@ -318,6 +391,19 @@ func (check *Checker) identical0(x, y Type, cmpTags bool, p *ifacePair) bool {
        return false
 }
 
+func (check *Checker) identicalTParams(x, y []*TypeName, cmpTags bool, p *ifacePair) bool {
+       if len(x) != len(y) {
+               return false
+       }
+       for i, x := range x {
+               y := y[i]
+               if !check.identical0(x.typ.(*TypeParam).bound, y.typ.(*TypeParam).bound, cmpTags, p) {
+                       return false
+               }
+       }
+       return true
+}
+
 // Default returns the default "typed" type for an "untyped" type;
 // it returns the incoming type for all other types. The default type
 // for untyped nil is untyped nil.