From 09abd23d9efecda2cec40ef6b8ca2cd93e220b40 Mon Sep 17 00:00:00 2001 From: Rob Findley Date: Tue, 15 Dec 2020 18:38:53 -0500 Subject: [PATCH] [dev.typeparams] go/types: import predicates.go from dev.go2go 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 Run-TryBot: Robert Findley TryBot-Result: Go Bot Reviewed-by: Robert Griesemer --- src/go/types/expr.go | 44 +++++---- src/go/types/predicates.go | 178 +++++++++++++++++++++++++++---------- 2 files changed, 159 insertions(+), 63 deletions(-) diff --git a/src/go/types/expr.go b/src/go/types/expr.go index 51714790cb..c57edf8f0d 100644 --- a/src/go/types/expr.go +++ b/src/go/types/expr.go @@ -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 - token.AND: isInteger, - token.OR: isInteger, - token.XOR: isInteger, - token.AND_NOT: isInteger, +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.LAND: isBoolean, - token.LOR: isBoolean, + token.AND: isInteger, + token.OR: isInteger, + token.XOR: isInteger, + token.AND_NOT: isInteger, + + token.LAND: isBoolean, + token.LOR: isBoolean, + } } // The binary expression e may be nil. It's passed in for better error messages only. diff --git a/src/go/types/predicates.go b/src/go/types/predicates.go index d580796d98..85e2b9a0ca 100644 --- a/src/go/types/predicates.go +++ b/src/go/types/predicates.go @@ -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 + return false } -func isBoolean(typ Type) bool { - t, ok := typ.Underlying().(*Basic) - return ok && t.info&IsBoolean != 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 isInteger(typ Type) bool { - t, ok := typ.Underlying().(*Basic) - return ok && t.info&IsInteger != 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 isUnsigned(typ Type) bool { - t, ok := typ.Underlying().(*Basic) - return ok && t.info&IsUnsigned != 0 -} +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) } -func isFloat(typ Type) bool { - t, ok := typ.Underlying().(*Basic) - return ok && t.info&IsFloat != 0 -} +// 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) } -func isComplex(typ Type) bool { - t, ok := typ.Underlying().(*Basic) - return ok && t.info&IsComplex != 0 -} - -func isNumeric(typ Type) bool { - t, ok := typ.Underlying().(*Basic) - return ok && t.info&IsNumeric != 0 -} - -func isString(typ Type) bool { - t, ok := typ.Underlying().(*Basic) - return ok && t.info&IsString != 0 -} +// 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.