From 9bfe09d78bd1b3ab97bc6e1c31395f0822875fba Mon Sep 17 00:00:00 2001 From: Dan Scales Date: Sun, 12 Dec 2021 11:08:59 -0800 Subject: [PATCH] cmd/compile: fix identity case relating to 'any' and shape types In identical(), we don't want any to match a shape empty-interface type for the identStrict option, since IdenticalStrict() is specifically not supposed to match a shape type with a non-shape type. There is similar code in (*Type).cmp() (TINTER case), but I don't believe that we want to disqualify shape types from matching any in this case, since cmp() is used for back-end code, where we don't care about shape types vs non-shape types. The issue mainly comes about when 'any' is used as a type argument (rather than 'interface{}'), but only with some complicated circumstances, as shown by the test case. (Couldn't reproduce with simpler test cases.) Fixes #50109 Change-Id: I3f2f88be158f9ad09273237e1d346bc56aac099f Reviewed-on: https://go-review.googlesource.com/c/go/+/371154 Trust: Dan Scales Run-TryBot: Dan Scales TryBot-Result: Gopher Robot Reviewed-by: Keith Randall --- src/cmd/compile/internal/types/identity.go | 6 +- test/typeparam/issue50109.go | 105 +++++++++++++++++++++ test/typeparam/issue50109.out | 1 + 3 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 test/typeparam/issue50109.go create mode 100644 test/typeparam/issue50109.out diff --git a/src/cmd/compile/internal/types/identity.go b/src/cmd/compile/internal/types/identity.go index f99e50a1c3..a164b84da9 100644 --- a/src/cmd/compile/internal/types/identity.go +++ b/src/cmd/compile/internal/types/identity.go @@ -59,7 +59,11 @@ func identical(t1, t2 *Type, flags int, assumedEqual map[typePair]struct{}) bool case TINT32: return (t1 == Types[TINT32] || t1 == RuneType) && (t2 == Types[TINT32] || t2 == RuneType) case TINTER: - // Make sure named any type matches any empty interface. + // Make sure named any type matches any empty interface + // (but not a shape type, if identStrict). + if flags&identStrict != 0 { + return t1 == AnyType && t2.IsEmptyInterface() && !t2.HasShape() || t2 == AnyType && t1.IsEmptyInterface() && !t1.HasShape() + } return t1 == AnyType && t2.IsEmptyInterface() || t2 == AnyType && t1.IsEmptyInterface() default: return false diff --git a/test/typeparam/issue50109.go b/test/typeparam/issue50109.go new file mode 100644 index 0000000000..a6913df843 --- /dev/null +++ b/test/typeparam/issue50109.go @@ -0,0 +1,105 @@ +// run -gcflags=-G=3 + +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" +) + +type AnyCacher[T any] interface { + // Get an item from the cache. Returns the item or nil, and a bool indicating + // whether the key was found. + Get(k string) (T, bool) + // Add an item to the cache, replacing any existing item. + Set(k string, x T) +} + +// Item ... +type Item[T any] struct { + Object T +} + +// AnyCache implements AnyCacher +type AnyCache[T any] struct { + *anyCache[T] +} + +type anyCache[T any] struct { + items map[string]Item[T] + janitor *janitor[T] // Needed for the failure in the issue +} + +// Set adds an item to the cache, replacing any existing item. +func (c *anyCache[T]) Set(k string, x T) { + c.items[k] = Item[T]{ + Object: x, + } +} + +// Get gets an item from the cache. Returns the item or nil, and a bool indicating +// whether the key was found. +func (c *anyCache[T]) Get(k string) (T, bool) { + // "Inlining" of get and Expired + item, found := c.items[k] + if !found { + var ret T + return ret, false + } + + return item.Object, true +} + +type janitor[T any] struct { + stop chan bool +} + +func newAnyCache[T any](m map[string]Item[T]) *anyCache[T] { + c := &anyCache[T]{ + items: m, + } + return c +} + +// NewAny[T any](...) returns a new AnyCache[T]. +func NewAny[T any]() *AnyCache[T] { + items := make(map[string]Item[T]) + return &AnyCache[T]{newAnyCache(items)} +} + +// NewAnyCacher[T any](...) returns an AnyCacher[T] interface. +func NewAnyCacher[T any]() AnyCacher[T] { + return NewAny[T]() +} + +type MyStruct struct { + Name string +} + +func main() { + // Create a generic cache. + // All items are cached as interface{} so they need to be cast back to their + // original type when retrieved. + // Failure in issue doesn't happen with 'any' replaced by 'interface{}' + c := NewAnyCacher[any]() + + myStruct := &MyStruct{"MySuperStruct"} + + c.Set("MySuperStruct", myStruct) + + myRawCachedStruct, found := c.Get("MySuperStruct") + + if found { + // Casting the retrieved object back to its original type + myCachedStruct := myRawCachedStruct.(*MyStruct) + fmt.Printf("%s", myCachedStruct.Name) + } else { + fmt.Printf("Error: MySuperStruct not found in cache") + } + + // Output: + // MySuperStruct +} diff --git a/test/typeparam/issue50109.out b/test/typeparam/issue50109.out new file mode 100644 index 0000000000..7d6ecc0c6d --- /dev/null +++ b/test/typeparam/issue50109.out @@ -0,0 +1 @@ +MySuperStruct \ No newline at end of file