| // Copyright 2022 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 ssa |
| |
| // Note: Tests use unexported functions. |
| |
| import ( |
| "bytes" |
| "go/types" |
| "reflect" |
| "sort" |
| "testing" |
| |
| "golang.org/x/tools/go/loader" |
| "golang.org/x/tools/internal/typeparams" |
| ) |
| |
| // TestNeedsInstance ensures that new method instances can be created via needsInstance, |
| // that TypeArgs are as expected, and can be accessed via _Instances. |
| func TestNeedsInstance(t *testing.T) { |
| if !typeparams.Enabled { |
| return |
| } |
| const input = ` |
| package p |
| |
| import "unsafe" |
| |
| type Pointer[T any] struct { |
| v unsafe.Pointer |
| } |
| |
| func (x *Pointer[T]) Load() *T { |
| return (*T)(LoadPointer(&x.v)) |
| } |
| |
| func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer) |
| ` |
| // The SSA members for this package should look something like this: |
| // func LoadPointer func(addr *unsafe.Pointer) (val unsafe.Pointer) |
| // type Pointer struct{v unsafe.Pointer} |
| // method (*Pointer[T any]) Load() *T |
| // func init func() |
| // var init$guard bool |
| |
| // Parse |
| var conf loader.Config |
| f, err := conf.ParseFile("<input>", input) |
| if err != nil { |
| t.Fatalf("parse: %v", err) |
| } |
| conf.CreateFromFiles("p", f) |
| |
| // Load |
| lprog, err := conf.Load() |
| if err != nil { |
| t.Fatalf("Load: %v", err) |
| } |
| |
| // Create and build SSA |
| prog := NewProgram(lprog.Fset, 0) |
| |
| for _, info := range lprog.AllPackages { |
| prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable) |
| } |
| |
| p := prog.Package(lprog.Package("p").Pkg) |
| p.Build() |
| |
| ptr := p.Type("Pointer").Type().(*types.Named) |
| if ptr.NumMethods() != 1 { |
| t.Fatalf("Expected Pointer to have 1 method. got %d", ptr.NumMethods()) |
| } |
| |
| obj := ptr.Method(0) |
| if obj.Name() != "Load" { |
| t.Errorf("Expected Pointer to have method named 'Load'. got %q", obj.Name()) |
| } |
| |
| meth := prog.FuncValue(obj) |
| |
| var cr creator |
| intSliceTyp := types.NewSlice(types.Typ[types.Int]) |
| instance := prog.needsInstance(meth, []types.Type{intSliceTyp}, &cr) |
| if len(cr) != 1 { |
| t.Errorf("Expected first instance to create a function. got %d created functions", len(cr)) |
| } |
| if instance._Origin != meth { |
| t.Errorf("Expected Origin of %s to be %s. got %s", instance, meth, instance._Origin) |
| } |
| if len(instance._TypeArgs) != 1 || !types.Identical(instance._TypeArgs[0], intSliceTyp) { |
| t.Errorf("Expected TypeArgs of %s to be %v. got %v", instance, []types.Type{intSliceTyp}, instance._TypeArgs) |
| } |
| instances := prog._Instances(meth) |
| if want := []*Function{instance}; !reflect.DeepEqual(instances, want) { |
| t.Errorf("Expected instances of %s to be %v. got %v", meth, want, instances) |
| } |
| |
| // A second request with an identical type returns the same Function. |
| second := prog.needsInstance(meth, []types.Type{types.NewSlice(types.Typ[types.Int])}, &cr) |
| if second != instance || len(cr) != 1 { |
| t.Error("Expected second identical instantiation to not create a function") |
| } |
| |
| // Add a second instance. |
| inst2 := prog.needsInstance(meth, []types.Type{types.NewSlice(types.Typ[types.Uint])}, &cr) |
| instances = prog._Instances(meth) |
| |
| // Note: instance.Name() < inst2.Name() |
| sort.Slice(instances, func(i, j int) bool { |
| return instances[i].Name() < instances[j].Name() |
| }) |
| if want := []*Function{instance, inst2}; !reflect.DeepEqual(instances, want) { |
| t.Errorf("Expected instances of %s to be %v. got %v", meth, want, instances) |
| } |
| |
| // build and sanity check manually created instance. |
| var b builder |
| b.buildFunction(instance) |
| var buf bytes.Buffer |
| if !sanityCheck(instance, &buf) { |
| t.Errorf("sanityCheck of %s failed with: %s", instance, buf.String()) |
| } |
| } |